From 7ee107a8d244fe70d02e5b6e3ed52530019a28b2 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Wed, 27 Dec 2023 11:19:43 -0800 Subject: [PATCH 1/7] Add new `EuiFlyoutResizable` component TODO: test refs --- .../flyout/flyout_resizable.styles.ts | 24 +++ src/components/flyout/flyout_resizable.tsx | 151 ++++++++++++++++++ src/components/flyout/index.ts | 3 + 3 files changed, 178 insertions(+) create mode 100644 src/components/flyout/flyout_resizable.styles.ts create mode 100644 src/components/flyout/flyout_resizable.tsx diff --git a/src/components/flyout/flyout_resizable.styles.ts b/src/components/flyout/flyout_resizable.styles.ts new file mode 100644 index 00000000000..b1376eea56c --- /dev/null +++ b/src/components/flyout/flyout_resizable.styles.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { css } from '@emotion/react'; + +import { UseEuiTheme } from '../../services'; + +export const euiFlyoutResizableButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ + euiFlyoutResizableButton: css` + position: absolute; + inset-inline-start: -${euiTheme.border.width.thin}; + + /* Hide the default grab icon (although the hover/focus states should remain) */ + &::before, + &::after { + background-color: transparent; + } + `, +}); diff --git a/src/components/flyout/flyout_resizable.tsx b/src/components/flyout/flyout_resizable.tsx new file mode 100644 index 00000000000..af62c2beb4d --- /dev/null +++ b/src/components/flyout/flyout_resizable.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { + forwardRef, + useState, + useEffect, + useRef, + useCallback, +} from 'react'; + +import { keys, useCombinedRefs, useEuiTheme } from '../../services'; +import { EuiResizableButton } from '../resizable_container'; + +import { EuiFlyout, EuiFlyoutProps } from './flyout'; +import { euiFlyoutResizableButtonStyles } from './flyout_resizable.styles'; + +export type EuiFlyoutResizableProps = Omit & { + maxWidth?: number; + minWidth?: number; +}; + +export const EuiFlyoutResizable = forwardRef( + ( + { + size, + maxWidth, + minWidth = 200, + children, + ...rest + }: EuiFlyoutResizableProps, + ref + ) => { + const euiTheme = useEuiTheme(); + const styles = euiFlyoutResizableButtonStyles(euiTheme); + const cssStyles = [styles.euiFlyoutResizableButton]; + + const getFlyoutMinMaxWidth = useCallback( + (width: number) => { + return Math.min( + Math.max(width, minWidth), + maxWidth || Infinity, + window.innerWidth - 20 // Leave some offset + ); + }, + [minWidth, maxWidth] + ); + + const [flyoutWidth, setFlyoutWidth] = useState(0); + + // 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(() => { + setFlyoutWidth(flyoutRef?.offsetWidth ?? 0); + }, [flyoutRef, size]); + + // Initial numbers to calculate from, on resize drag start + const initialWidth = useRef(0); + const initialMouseX = useRef(0); + + const onMouseMove = useCallback( + (e: MouseEvent | TouchEvent) => { + const mouseOffset = getMouseOrTouchX(e) - initialMouseX.current; + const changedFlyoutWidth = initialWidth.current - mouseOffset; + + setFlyoutWidth(getFlyoutMinMaxWidth(changedFlyoutWidth)); + }, + [getFlyoutMinMaxWidth] + ); + + const onMouseUp = useCallback(() => { + initialMouseX.current = 0; + + window.removeEventListener('mousemove', onMouseMove); + window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('touchmove', onMouseMove); + window.removeEventListener('touchend', onMouseUp); + }, [onMouseMove]); + + const onMouseDown = useCallback( + (e: React.MouseEvent | React.TouchEvent) => { + initialMouseX.current = getMouseOrTouchX(e); + initialWidth.current = flyoutRef?.offsetWidth ?? 0; + + // Window event listeners instead of React events are used + // in case the user's mouse leaves the component + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + window.addEventListener('touchmove', onMouseMove); + window.addEventListener('touchend', onMouseUp); + }, + [flyoutRef, onMouseMove, onMouseUp] + ); + + const onKeyDown = useCallback( + (e: React.KeyboardEvent) => { + const KEYBOARD_OFFSET = 10; + + switch (e.key) { + case keys.ARROW_RIGHT: + e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise + setFlyoutWidth((flyoutWidth) => + getFlyoutMinMaxWidth(flyoutWidth - KEYBOARD_OFFSET) + ); + break; + case keys.ARROW_LEFT: + e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise + setFlyoutWidth((flyoutWidth) => + getFlyoutMinMaxWidth(flyoutWidth + KEYBOARD_OFFSET) + ); + } + }, + [getFlyoutMinMaxWidth] + ); + + return ( + + + {children} + + ); + } +); +EuiFlyoutResizable.displayName = 'EuiFlyoutResizable'; + +const getMouseOrTouchX = ( + e: TouchEvent | MouseEvent | React.MouseEvent | React.TouchEvent +): number => { + // Some Typescript fooling is needed here + const x = (e as TouchEvent).targetTouches + ? (e as TouchEvent).targetTouches[0].pageX + : (e as MouseEvent).pageX; + return x; +}; diff --git a/src/components/flyout/index.ts b/src/components/flyout/index.ts index e1cd2bed913..aa80a23d85a 100644 --- a/src/components/flyout/index.ts +++ b/src/components/flyout/index.ts @@ -19,3 +19,6 @@ export type { EuiFlyoutHeaderProps } from './flyout_header'; export { EuiFlyoutHeader } from './flyout_header'; export { euiFlyoutSlideInRight, euiFlyoutSlideInLeft } from './flyout.styles'; + +export type { EuiFlyoutResizableProps } from './flyout_resizable'; +export { EuiFlyoutResizable } from './flyout_resizable'; From f611677618e61b4eb04770192d19538a06eb180b Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 28 Dec 2023 12:29:01 -0800 Subject: [PATCH 2/7] Write tests - significantly easier to write in Cypress/E2E than in jsdom - much less width mocking (although there's still some with mouse/touch events) - add fix for initial size going outside specified allowed min/maxes (yay TDD) --- .../flyout/flyout_resizable.spec.tsx | 129 ++++++++++++++++++ src/components/flyout/flyout_resizable.tsx | 6 +- 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/components/flyout/flyout_resizable.spec.tsx diff --git a/src/components/flyout/flyout_resizable.spec.tsx b/src/components/flyout/flyout_resizable.spec.tsx new file mode 100644 index 00000000000..d5e7c2400a4 --- /dev/null +++ b/src/components/flyout/flyout_resizable.spec.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/// +/// +/// + +import React from 'react'; + +import { EuiFlyoutResizable } from './flyout_resizable'; + +const onClose = () => {}; + +describe('EuiFlyoutResizable', () => { + beforeEach(() => { + cy.viewport(1200, 500); + }); + + describe('sets the flyout size after initial load to a static number', () => { + it('default size', () => { + cy.mount(); + cy.get('.euiFlyout').should('have.css', 'inline-size', '600px'); + }); + + it('size enum', () => { + cy.mount(); + cy.get('.euiFlyout').should('have.css', 'inline-size', '384px'); + }); + + it('number', () => { + cy.mount(); + cy.get('.euiFlyout').should('have.css', 'inline-size', '300px'); + }); + + it('CSS width', () => { + cy.mount(); + cy.get('.euiFlyout').should('have.css', 'inline-size', '960px'); + }); + + it('with max width', () => { + cy.mount( + + ); + cy.get('.euiFlyout').should('have.css', 'inline-size', '400px'); + }); + + it('with min width', () => { + cy.mount( + + ); + cy.get('.euiFlyout').should('have.css', 'inline-size', '200px'); + }); + }); + + describe('resizing', () => { + // There isn't a way to actually drag & drop in Cypress, so we're mocking it via triggers + it('mouse drag', () => { + cy.mount(); + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 400 }) + .trigger('mousemove', { pageX: 600 }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '600px'); + + cy.get('[data-test-subj="euiResizableButton"]').trigger('mousemove', { + pageX: 200, + }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '1000px'); + + // Should not change the flyout width if not dragging + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mouseup') + .trigger('mousemove', { pageX: 1000 }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '1000px'); + }); + + it('mobile touch drag', () => { + cy.mount(); + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('touchstart', { targetTouches: [{ pageX: 400 }], touches: [] }) + .trigger('touchmove', { targetTouches: [{ pageX: 800 }], touches: [] }) + .trigger('touchend', { touches: [] }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '400px'); + }); + + it('keyboard tabbing', () => { + cy.mount(); + cy.get('[data-test-subj="euiResizableButton"]').focus(); + + cy.repeatRealPress('ArrowRight', 10); + cy.get('.euiFlyout').should('have.css', 'inline-size', '700px'); + + cy.repeatRealPress('ArrowLeft', 5); + cy.get('.euiFlyout').should('have.css', 'inline-size', '750px'); + }); + + it('does not allow the flyout to be resized past the window width', () => { + cy.mount(); + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 400 }) + .trigger('mousemove', { pageX: -100 }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '1180px'); + }); + + it('does not allow the flyout to be resized past the max width', () => { + cy.mount( + + ); + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 400 }) + .trigger('mousemove', { pageX: 100 }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '1000px'); + }); + + it('does not allow the flyout to be resized past the min width', () => { + cy.mount( + + ); + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 400 }) + .trigger('mousemove', { pageX: 2000 }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '100px'); + }); + }); +}); diff --git a/src/components/flyout/flyout_resizable.tsx b/src/components/flyout/flyout_resizable.tsx index af62c2beb4d..39349ac95c6 100644 --- a/src/components/flyout/flyout_resizable.tsx +++ b/src/components/flyout/flyout_resizable.tsx @@ -57,8 +57,10 @@ export const EuiFlyoutResizable = forwardRef( const [flyoutRef, setFlyoutRef] = useState(null); const setRefs = useCombinedRefs([setFlyoutRef, ref]); useEffect(() => { - setFlyoutWidth(flyoutRef?.offsetWidth ?? 0); - }, [flyoutRef, size]); + setFlyoutWidth( + flyoutRef ? getFlyoutMinMaxWidth(flyoutRef.offsetWidth) : 0 + ); + }, [flyoutRef, getFlyoutMinMaxWidth, size]); // Initial numbers to calculate from, on resize drag start const initialWidth = useRef(0); From 82a50923e0c67c119280d333313beeb1f1f1d5db Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Wed, 27 Dec 2023 11:57:41 -0800 Subject: [PATCH 3/7] Support left side flyouts and logical directions --- .../flyout/flyout_resizable.spec.tsx | 35 +++++++++++++++++++ .../flyout/flyout_resizable.styles.ts | 8 ++++- src/components/flyout/flyout_resizable.tsx | 26 ++++++++++---- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/components/flyout/flyout_resizable.spec.tsx b/src/components/flyout/flyout_resizable.spec.tsx index d5e7c2400a4..00627bf9fdc 100644 --- a/src/components/flyout/flyout_resizable.spec.tsx +++ b/src/components/flyout/flyout_resizable.spec.tsx @@ -125,5 +125,40 @@ describe('EuiFlyoutResizable', () => { .trigger('mousemove', { pageX: 2000 }); cy.get('.euiFlyout').should('have.css', 'inline-size', '100px'); }); + + describe('direction', () => { + it('reverses the calculations for left side flyouts', () => { + cy.mount( + + ); + assertReversedDirections(); + }); + + it('reverses again for RTL logical property directions', () => { + cy.mount( + + ); + assertReversedDirections({ force: true }); + }); + + const assertReversedDirections = (options?: { force: boolean }) => { + cy.get('[data-test-subj="euiResizableButton"]').focus(); + + cy.repeatRealPress('ArrowRight', 10); + cy.get('.euiFlyout').should('have.css', 'inline-size', '900px'); + + cy.repeatRealPress('ArrowLeft', 5); + cy.get('.euiFlyout').should('have.css', 'inline-size', '850px'); + + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 850, ...options }) + .trigger('mousemove', { pageX: 400, ...options }); + cy.get('.euiFlyout').should('have.css', 'inline-size', '400px'); + }; + }); }); }); diff --git a/src/components/flyout/flyout_resizable.styles.ts b/src/components/flyout/flyout_resizable.styles.ts index b1376eea56c..abcda128fef 100644 --- a/src/components/flyout/flyout_resizable.styles.ts +++ b/src/components/flyout/flyout_resizable.styles.ts @@ -9,11 +9,11 @@ import { css } from '@emotion/react'; import { UseEuiTheme } from '../../services'; +import { logicalCSS } from '../../global_styling'; export const euiFlyoutResizableButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ euiFlyoutResizableButton: css` position: absolute; - inset-inline-start: -${euiTheme.border.width.thin}; /* Hide the default grab icon (although the hover/focus states should remain) */ &::before, @@ -21,4 +21,10 @@ export const euiFlyoutResizableButtonStyles = ({ euiTheme }: UseEuiTheme) => ({ background-color: transparent; } `, + left: css` + ${logicalCSS('right', `-${euiTheme.border.width.thin}`)} + `, + right: css` + ${logicalCSS('left', `-${euiTheme.border.width.thin}`)} + `, }); diff --git a/src/components/flyout/flyout_resizable.tsx b/src/components/flyout/flyout_resizable.tsx index 39349ac95c6..0c97713edb7 100644 --- a/src/components/flyout/flyout_resizable.tsx +++ b/src/components/flyout/flyout_resizable.tsx @@ -11,6 +11,7 @@ import React, { useState, useEffect, useRef, + useMemo, useCallback, } from 'react'; @@ -31,6 +32,7 @@ export const EuiFlyoutResizable = forwardRef( size, maxWidth, minWidth = 200, + side = 'right', children, ...rest }: EuiFlyoutResizableProps, @@ -38,7 +40,7 @@ export const EuiFlyoutResizable = forwardRef( ) => { const euiTheme = useEuiTheme(); const styles = euiFlyoutResizableButtonStyles(euiTheme); - const cssStyles = [styles.euiFlyoutResizableButton]; + const cssStyles = [styles.euiFlyoutResizableButton, styles[side]]; const getFlyoutMinMaxWidth = useCallback( (width: number) => { @@ -66,14 +68,25 @@ export const EuiFlyoutResizable = forwardRef( const initialWidth = useRef(0); const initialMouseX = useRef(0); + // Account for flyout side and logical property direction + const direction = useMemo(() => { + let modifier = side === 'right' ? -1 : 1; + if (flyoutRef) { + const languageDirection = window.getComputedStyle(flyoutRef).direction; + if (languageDirection === 'rtl') modifier *= -1; + } + return modifier; + }, [side, flyoutRef]); + const onMouseMove = useCallback( (e: MouseEvent | TouchEvent) => { const mouseOffset = getMouseOrTouchX(e) - initialMouseX.current; - const changedFlyoutWidth = initialWidth.current - mouseOffset; + const changedFlyoutWidth = + initialWidth.current + mouseOffset * direction; setFlyoutWidth(getFlyoutMinMaxWidth(changedFlyoutWidth)); }, - [getFlyoutMinMaxWidth] + [getFlyoutMinMaxWidth, direction] ); const onMouseUp = useCallback(() => { @@ -108,17 +121,17 @@ export const EuiFlyoutResizable = forwardRef( case keys.ARROW_RIGHT: e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise setFlyoutWidth((flyoutWidth) => - getFlyoutMinMaxWidth(flyoutWidth - KEYBOARD_OFFSET) + getFlyoutMinMaxWidth(flyoutWidth + KEYBOARD_OFFSET * direction) ); break; case keys.ARROW_LEFT: e.preventDefault(); // Safari+VO will screen reader navigate off the button otherwise setFlyoutWidth((flyoutWidth) => - getFlyoutMinMaxWidth(flyoutWidth + KEYBOARD_OFFSET) + getFlyoutMinMaxWidth(flyoutWidth - KEYBOARD_OFFSET * direction) ); } }, - [getFlyoutMinMaxWidth] + [getFlyoutMinMaxWidth, direction] ); return ( @@ -126,6 +139,7 @@ export const EuiFlyoutResizable = forwardRef( {...rest} size={flyoutWidth || size} maxWidth={maxWidth} + side={side} ref={setRefs} > Date: Thu, 28 Dec 2023 12:41:34 -0800 Subject: [PATCH 4/7] Resizable push flyout tests + convert body padding inline styles to logical properties --- .../collapsible_nav_beta.test.tsx.snap | 4 +-- src/components/flyout/flyout.tsx | 8 ++--- .../flyout/flyout_resizable.spec.tsx | 33 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/components/collapsible_nav_beta/__snapshots__/collapsible_nav_beta.test.tsx.snap b/src/components/collapsible_nav_beta/__snapshots__/collapsible_nav_beta.test.tsx.snap index 28fe32c136d..ba7b15a27bd 100644 --- a/src/components/collapsible_nav_beta/__snapshots__/collapsible_nav_beta.test.tsx.snap +++ b/src/components/collapsible_nav_beta/__snapshots__/collapsible_nav_beta.test.tsx.snap @@ -3,7 +3,7 @@ exports[`EuiCollapsibleNavBeta renders 1`] = `
{ }; }); }); + + describe('push flyouts', () => { + it('correctly updates the body padding offset on resize', () => { + cy.mount(); + cy.get('body').should('have.css', 'padding-inline-end', '800px'); + + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 400 }) + .trigger('mousemove', { pageX: 1000 }); + + cy.get('.euiFlyout').should('have.css', 'inline-size', '200px'); + cy.get('body').should('have.css', 'padding-inline-end', '200px'); + }); + + it('handles left side push flyouts', () => { + cy.mount( + + ); + cy.get('body').should('have.css', 'padding-inline-start', '800px'); + + cy.get('[data-test-subj="euiResizableButton"]') + .trigger('mousedown', { pageX: 800 }) + .trigger('mousemove', { pageX: 200 }); + + cy.get('.euiFlyout').should('have.css', 'inline-size', '200px'); + cy.get('body').should('have.css', 'padding-inline-start', '200px'); + }); + }); }); From 9f3dc57d4e63cc7f9ba5f20980172c16da528e2f Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Wed, 27 Dec 2023 12:02:19 -0800 Subject: [PATCH 5/7] Add docs example --- src-docs/src/views/flyout/flyout_example.js | 46 ++++++++++ .../src/views/flyout/flyout_resizable.tsx | 89 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 src-docs/src/views/flyout/flyout_resizable.tsx diff --git a/src-docs/src/views/flyout/flyout_example.js b/src-docs/src/views/flyout/flyout_example.js index 5097bfb0c7b..fbe28aa1633 100644 --- a/src-docs/src/views/flyout/flyout_example.js +++ b/src-docs/src/views/flyout/flyout_example.js @@ -9,6 +9,7 @@ import { EuiFlyoutBody, EuiFlyoutHeader, EuiFlyoutFooter, + EuiFlyoutResizable, EuiCallOut, EuiLink, } from '../../../../src/components'; @@ -37,6 +38,9 @@ const flyoutWithBannerSource = require('!!raw-loader!./flyout_banner'); import FlyoutPush from './flyout_push'; const flyoutPushSource = require('!!raw-loader!./flyout_push'); +import FlyoutResizable from './flyout_resizable'; +const flyoutResizableSource = require('!!raw-loader!./flyout_resizable'); + const flyOutSnippet = ` @@ -144,6 +148,18 @@ const flyoutPushedSnippet = ` `; +const flyoutResizableSnippet = ` + + +

Flyout title

+
+
+ + + +
+`; + export const FlyoutExample = { title: 'Flyout', sections: [ @@ -384,5 +400,35 @@ export const FlyoutExample = { demo: , props: { EuiFlyout }, }, + { + title: 'Resizable flyouts', + source: [ + { + type: GuideSectionTypes.JS, + code: flyoutResizableSource, + }, + ], + text: ( + <> +

+ You can use EuiFlyoutResizable to render a flyout + that users can drag with their mouse or use arrow keys to resize. + This may be useful for scenarios where the space the user needs can + be unpredictable, if content is dynamic. Resizable flyouts allow + users to adjust content to better fit their individual screens and + workflows. +

+

+ We strongly recommend setting reasonable numerical{' '} + minWidth and maxWidth props + based on the flyout content and page content that you can{' '} + predict. +

+ + ), + snippet: flyoutResizableSnippet, + demo: , + props: { EuiFlyoutResizable }, + }, ], }; diff --git a/src-docs/src/views/flyout/flyout_resizable.tsx b/src-docs/src/views/flyout/flyout_resizable.tsx new file mode 100644 index 00000000000..4969860ca89 --- /dev/null +++ b/src-docs/src/views/flyout/flyout_resizable.tsx @@ -0,0 +1,89 @@ +import React, { useState } from 'react'; + +import { + EuiFlyoutResizable, + EuiFlyoutProps, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiButton, + EuiButtonGroup, + EuiText, + EuiTitle, + EuiSpacer, +} from '../../../../src/components'; + +import { useGeneratedHtmlId } from '../../../../src/services'; + +export default () => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [flyoutType, setFlyoutType] = useState('overlay'); + const [flyoutSide, setFlyoutSide] = useState('right'); + + const flyoutTitleId = useGeneratedHtmlId({ + prefix: 'simpleFlyoutTitle', + }); + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + setIsFlyoutVisible(false)} + aria-labelledby={flyoutTitleId} + > + + +

A resizable flyout

+
+
+ + +

+ This flyout is resizable by both mouse drag and arrow keys (when + the resizable edge is focused). Both push and overlay flyouts can + be resizable, on either side. +

+
+ + +

Flyout type

+
+ + setFlyoutType(id)} + /> + + +

Flyout side

+
+ setFlyoutSide(id)} + /> +
+
+ ); + } + + return ( + <> + setIsFlyoutVisible(true)}> + Show resizable flyout + + {flyout} + + ); +}; From 579065a2151b82969bb1ce7b0a6bc3f4e3fc2bb9 Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Thu, 28 Dec 2023 12:57:59 -0800 Subject: [PATCH 6/7] changelog --- changelogs/upcoming/7439.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelogs/upcoming/7439.md diff --git a/changelogs/upcoming/7439.md b/changelogs/upcoming/7439.md new file mode 100644 index 00000000000..53ede186f68 --- /dev/null +++ b/changelogs/upcoming/7439.md @@ -0,0 +1 @@ +- Added a new `EuiFlyoutResizable` component From 0fa3594252ec3ac4d4d73e1ce7dcd81d575a4c2d Mon Sep 17 00:00:00 2001 From: Cee Chen Date: Fri, 5 Jan 2024 13:25:47 -0800 Subject: [PATCH 7/7] Add beta flag in docs --- src-docs/src/views/flyout/flyout_example.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src-docs/src/views/flyout/flyout_example.js b/src-docs/src/views/flyout/flyout_example.js index fbe28aa1633..2a894039a23 100644 --- a/src-docs/src/views/flyout/flyout_example.js +++ b/src-docs/src/views/flyout/flyout_example.js @@ -402,6 +402,7 @@ export const FlyoutExample = { }, { title: 'Resizable flyouts', + isBeta: true, source: [ { type: GuideSectionTypes.JS,