Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/8999.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added a new optional `resizable` (boolean) prop to `EuiFlyout`. Resizability can now be controlled dynamically without the need to use `EuiFlyoutResizable`.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { EuiCollapsibleNavButton } from './collapsible_nav_button';
import { euiCollapsibleNavBetaStyles } from './collapsible_nav_beta.styles';

export type EuiCollapsibleNavBetaProps = CommonProps &
HTMLAttributes<HTMLElement> &
Omit<HTMLAttributes<HTMLElement>, 'onResize'> &
Pick<
EuiFlyoutProps, // Extend only specific flyout props - EuiCollapsibleNav is much less customizable than EuiFlyout
'side' | 'focusTrapProps' | 'includeFixedHeadersInFocusTrap'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1051,52 +1051,58 @@ exports[`EuiFlyout props size accepts custom number 1`] = `
`;

exports[`EuiFlyout props size fill is rendered 1`] = `
[
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an updated snapshot that was failing on feat/flyout-system due to last-minute changes on the last PR merged to the feature branch. The changes match other "is rendered" variants

<body
class="euiBody--hasFlyout"
style=""
>
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
data-focus-lock-disabled="false"
>
<div>
<div
aria-describedby="generated-id"
aria-modal="true"
class="euiFlyout emotion-euiFlyout-l-fill-noMaxWidth-overlay-right-right"
data-autofocus="true"
role="dialog"
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
data-focus-lock-disabled="false"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id"
>
You are in a modal dialog. Press Escape or tap/click outside the dialog on the shadowed overlay to close.
</p>
<button
aria-label="Close this dialog"
class="euiButtonIcon euiFlyout__closeButton emotion-euiButtonIcon-xs-empty-text-euiFlyout__closeButton-inside"
data-test-subj="euiFlyoutCloseButton"
type="button"
<div
aria-describedby="generated-id"
aria-modal="true"
class="euiFlyout emotion-euiFlyout-l-fill-noMaxWidth-overlay-right-right"
data-autofocus="true"
role="dialog"
tabindex="0"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id"
>
You are in a modal dialog. Press Escape or tap/click outside the dialog on the shadowed overlay to close.

</p>
<button
aria-label="Close this dialog"
class="euiButtonIcon euiFlyout__closeButton emotion-euiButtonIcon-xs-empty-text-euiFlyout__closeButton-inside"
data-test-subj="euiFlyoutCloseButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="cross"
/>
</button>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>,
]
</div>
</body>
`;

exports[`EuiFlyout props size l is rendered 1`] = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
*/

import { css } from '@emotion/react';

import { UseEuiTheme } from '../../services';
import { logicalCSS } from '../../global_styling';
import { UseEuiTheme } from '../../services';

export const euiFlyoutResizableButtonStyles = ({ euiTheme }: UseEuiTheme) => ({
euiFlyoutResizableButton: css`
export const euiFlyoutResizeButtonStyles = ({ euiTheme }: UseEuiTheme) => ({
root: css`
position: absolute;
`,
overlay: {
Expand All @@ -32,7 +31,7 @@ export const euiFlyoutResizableButtonStyles = ({ euiTheme }: UseEuiTheme) => ({
`,
},
noOverlay: {
noOverlay: css`
root: css`
margin-inline: 0;
`,
left: css`
Expand Down
54 changes: 54 additions & 0 deletions packages/eui/src/components/flyout/_flyout_resize_button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 { useEuiMemoizedStyles } from '../../services';
import {
EuiResizableButton,
EuiResizableButtonProps,
} from '../resizable_container';
import type { _EuiFlyoutType, _EuiFlyoutSide } from './const';
import type { EuiFlyoutComponentProps } from './flyout.component';
import { euiFlyoutResizeButtonStyles } from './_flyout_resize_button.styles';

type EuiFlyoutResizeButtonProps = Pick<
EuiResizableButtonProps,
'onMouseDown' | 'onKeyDown' | 'onTouchStart'
> & {
type: _EuiFlyoutType;
side: _EuiFlyoutSide;
ownFocus: EuiFlyoutComponentProps['ownFocus'];
isPushed: boolean;
};

export const EuiFlyoutResizeButton = ({
type,
side,
ownFocus,
isPushed,
...resizableButtonProps
}: EuiFlyoutResizeButtonProps) => {
const hasOverlay = ownFocus && type === 'overlay';
const styles = useEuiMemoizedStyles(euiFlyoutResizeButtonStyles);

const cssStyles = [
styles.root,
styles[type][side],
!hasOverlay && styles.noOverlay.root,
!hasOverlay && styles.noOverlay[side],
];

return (
<EuiResizableButton
isHorizontal
indicator="border"
css={cssStyles}
{...resizableButtonProps}
/>
);
};
49 changes: 48 additions & 1 deletion packages/eui/src/components/flyout/flyout.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import {
isEuiFlyoutSizeNamed,
} from './const';
import { useIsPushed } from './hooks';
import { EuiFlyoutResizeButton } from './_flyout_resize_button';
import { useEuiFlyoutResizable } from './use_flyout_resizable';

interface _EuiFlyoutComponentProps {
onClose: (event: MouseEvent | TouchEvent | KeyboardEvent) => void;
Expand All @@ -76,6 +78,11 @@ interface _EuiFlyoutComponentProps {
* @default m
*/
size?: EuiFlyoutSize | CSSProperties['width'];
/**
* Sets the minimum width of the panel.
* Especially useful when set with `resizable = true`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be good to have this for fill mode calculations as well. In the side-by-side layout when a flyout has fill mode, we need to refer to this in our calculation for the breakpoint when the layout is switched to stacked. Right now the code is using non-dynamic s value as the minimum width.

*/
minWidth?: number;
/**
* Sets the max-width of the panel,
* set to `true` to use the default size,
Expand Down Expand Up @@ -166,6 +173,17 @@ interface _EuiFlyoutComponentProps {
* Specify additional css selectors to include in the focus trap.
*/
includeSelectorInFocusTrap?: string[] | string;

/**
* Whether the flyout should be resizable.
* @default false
*/
resizable?: boolean;

/**
* Optional callback that fires when the flyout is resized.
*/
onResize?: (width: number) => void;
}

const defaultElement = 'div';
Expand Down Expand Up @@ -199,7 +217,7 @@ export const EuiFlyoutComponent = forwardRef(
onClose,
ownFocus = true,
side = DEFAULT_SIDE,
size = DEFAULT_SIZE,
size: _size = DEFAULT_SIZE,
paddingSize = DEFAULT_PADDING_SIZE,
maxWidth = false,
style,
Expand All @@ -213,6 +231,9 @@ export const EuiFlyoutComponent = forwardRef(
includeSelectorInFocusTrap,
'aria-describedby': _ariaDescribedBy,
id,
resizable = false,
minWidth,
onResize,
...rest
} = usePropsWithComponentDefaults('EuiFlyout', props);

Expand All @@ -225,6 +246,20 @@ export const EuiFlyoutComponent = forwardRef(
const internalParentFlyoutRef = useRef<HTMLDivElement>(null);
const isPushed = useIsPushed({ type, pushMinBreakpoint });

const {
onMouseDown: onMouseDownResizableButton,
onKeyDown: onKeyDownResizableButton,
size,
setFlyoutRef,
} = useEuiFlyoutResizable({
enabled: resizable,
minWidth,
maxWidth: typeof maxWidth === 'number' ? maxWidth : 0,
onResize,
side,
size: _size,
});

/**
* Setting up the refs on the actual flyout element in order to
* accommodate for the `isPushed` state by adding padding to the body equal to the width of the element
Expand All @@ -236,6 +271,7 @@ export const EuiFlyoutComponent = forwardRef(
setResizeRef,
ref,
internalParentFlyoutRef,
setFlyoutRef,
]);
const { width } = useResizeObserver(isPushed ? resizeRef : null, 'width');

Expand Down Expand Up @@ -534,6 +570,17 @@ export const EuiFlyoutComponent = forwardRef(
side={side}
/>
)}
{resizable && (
<EuiFlyoutResizeButton
type={type}
side={side}
ownFocus={ownFocus}
isPushed={isPushed}
onMouseDown={onMouseDownResizableButton}
onTouchStart={onMouseDownResizableButton}
onKeyDown={onKeyDownResizableButton}
/>
)}
{children}
</Element>
</EuiFocusTrap>
Expand Down
Loading