Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
18 changes: 18 additions & 0 deletions packages/eui/changelogs/upcoming/9856.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
- Updated `EuiFlyoutMenu`:
- Added `leadingActions` and `trailingActions` props, which accept the new `EuiFlyoutMenuAction` type supporting `toolTipContent` and `toolTipProps`
- Added `leadingActions` support alongside pagination controls
- Changed the history trigger icon to `clockCounter` and added a "Recently visited" tooltip
- Added control-group dividers between built-in controls (pagination, or back/history) and `leadingActions`, and between `trailingActions` and the close button
- Added `previousIconType` and `nextIconType` to `pagination`, so consumers can swap the default up/down chevrons for e.g. left/right chevrons when paging through horizontally-oriented content
- Added `onFirst`, `onLast`, `firstIconType`, and `lastIconType` to `pagination`, which render optional buttons for jumping to the beginning and end of the list. Passing either handler also switches the default Prev/Next icons from up/down to left/right chevrons
- Changed the menu bar dividers to span the full height of the menu bar in high contrast mode, instead of the shortened line used by default

**Bug fixes**

- Fixed `EuiFlyoutMenu` rendering a history popover that duplicated the back button; the popover now only appears when `historyItems` has more than one entry, so managed flyouts with only one previous session show just the back button
- Fixed vertical alignment of `EuiFlyoutMenu` action buttons, which were a size larger than the other menu bar controls and overflowed the menu's bottom border. All menu bar controls, including the close button, now share a center line
- Fixed `EuiFlyoutMenu` pagination controls disappearing when `pagination.total` is `1`; the controls now render (as "1 of 1" with both Prev/Next disabled) for any `total` of at least `1`

**Deprecations**

- Deprecated `customActions` in `EuiFlyoutMenu`; use `trailingActions` instead
Original file line number Diff line number Diff line change
Expand Up @@ -95,57 +95,3 @@ exports[`EuiContextMenuItem renders 1`] = `
</span>
</a>
`;

exports[`EuiContextMenuItem tooltip behavior 1`] = `
<body
class="euiBody-hasPortalContent"
>
<div>
<span
class="euiToolTipAnchor eui-displayBlock emotion-euiToolTipAnchor-inlineBlock"
id="generated-id-wrapper"
>
<button
aria-describedby="generated-id"
class="euiListItemLayout euiContextMenuItem emotion-euiListItemLayout-isInteractive-euiContextMenuItem-center"
data-test-subj="euiListItemLayout"
type="button"
>
<span
class="euiListItemLayout__content emotion-euiListItemLayout__content"
>
<span
class="euiListItemLayout__text euiContextMenuItem__text emotion-euiListItemLayout__text-wrap-euiContextMenuItem__text"
>
Hello
</span>
</span>
</button>
</span>
</div>
<div
data-euiportal="true"
>
<div
class="euiToolTipPopover euiToolTip emotion-euiToolTip-right"
data-position="right"
id="generated-id"
role="tooltip"
style="top: -10px; left: 16px;"
>
<div
class="euiToolTip__title emotion-euiToolTip__title"
>
Test
</div>
<div
class="euiToolTip__arrow emotion-euiToolTip__arrow-right"
style="top: 3px; right: 100%;"
/>
<div>
tooltip content
</div>
</div>
</div>
</body>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ import { shouldRenderCustomStyles } from '../../test/internal';
import { requiredProps } from '../../test/required_props';

import { EuiContextMenuItem } from './context_menu_item';
import { toolTipManager } from '../tool_tip/tool_tip_manager';

describe('EuiContextMenuItem', () => {
// The manager is a module-level singleton that suppresses the entry animation
// for a tooltip opened shortly after another closed. Without a reset, one
// test's tooltip leaks an inline `animation: none` into the next one.
beforeEach(() => {
toolTipManager.reset();
});

shouldRenderCustomStyles(<EuiContextMenuItem />);

shouldRenderCustomStyles(
Expand Down Expand Up @@ -143,18 +151,56 @@ describe('EuiContextMenuItem', () => {
});
});

test('tooltip behavior', () => {
const { getByRole, baseElement } = render(
<EuiContextMenuItem
toolTipContent="tooltip content"
toolTipProps={{ title: 'Test', position: 'top' }}
>
Hello
</EuiContextMenuItem>
);
describe('tooltip behavior', () => {
const renderWithToolTip = () =>
render(
<EuiContextMenuItem
toolTipContent="tooltip content"
toolTipProps={{ title: 'Test', position: 'top' }}
>
Hello
</EuiContextMenuItem>
);

it('renders no tooltip until the item is hovered', () => {
const { queryByRole } = renderWithToolTip();

expect(queryByRole('tooltip')).not.toBeInTheDocument();
});

it('shows the tooltip title and content on hover', () => {
const { getByRole } = renderWithToolTip();

fireEvent.mouseOver(getByRole('button'));

fireEvent.mouseOver(getByRole('button'));
const tooltip = getByRole('tooltip');
expect(tooltip).toHaveTextContent('Test');
expect(tooltip).toHaveTextContent('tooltip content');
});

it('describes the item with the tooltip for assistive technology', () => {
const { getByRole } = renderWithToolTip();

fireEvent.mouseOver(getByRole('button'));

expect(getByRole('button')).toHaveAttribute(
'aria-describedby',
getByRole('tooltip').id
);
});

expect(baseElement).toMatchSnapshot();
it('forces the tooltip to the right, ignoring `toolTipProps.position`', () => {
const { getByRole } = renderWithToolTip();

fireEvent.mouseOver(getByRole('button'));

expect(getByRole('tooltip')).toHaveAttribute('data-position', 'right');
});

it('renders as a button so the tooltip is keyboard reachable', () => {
const { getByRole } = renderWithToolTip();

expect(getByRole('button')).toHaveAttribute('type', 'button');
});
});
});
8 changes: 8 additions & 0 deletions packages/eui/src/components/flyout/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export const FLYOUT_MENU_DISPLAY_MODES = [
export type EuiFlyoutMenuDisplayMode =
(typeof FLYOUT_MENU_DISPLAY_MODES)[number];

/**
* Minimum number of history items required to render the flyout menu's history
* popover. A single item is redundant with the back button, which already
* navigates to that same session, so the popover only appears once it offers
* more than one destination.
*/
export const MIN_HISTORY_ITEMS = 2;

/** Allowed padding sizes for flyout content. */
export const FLYOUT_PADDING_SIZES = ['none', 's', 'm', 'l'] as const;
/** Type representing a supported flyout padding size. */
Expand Down
14 changes: 8 additions & 6 deletions packages/eui/src/components/flyout/flyout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { DEFAULT_MENU_DISPLAY_MODE, FLYOUT_MENU_DISPLAY_MODES } from './const';

interface FlyoutStoryArgs extends EuiFlyoutProps {
onToggle?: (open: boolean) => void;
showCustomActions?: boolean;
showTrailingActions?: boolean;
}

const meta: Meta<FlyoutStoryArgs> = {
Expand All @@ -38,7 +38,7 @@ const meta: Meta<FlyoutStoryArgs> = {
control: { type: 'radio' },
description: 'The display mode of the flyout menu.',
},
showCustomActions: { control: 'boolean' },
showTrailingActions: { control: 'boolean' },
},
args: {
// Component defaults
Expand All @@ -53,7 +53,7 @@ const meta: Meta<FlyoutStoryArgs> = {
hideCloseButton: false,
ownFocus: true,
flyoutMenuDisplayMode: DEFAULT_MENU_DISPLAY_MODE,
showCustomActions: true,
showTrailingActions: true,
},
parameters: {
vrt: {
Expand All @@ -68,16 +68,18 @@ type Story = StoryObj<EuiFlyoutProps>;

const onClose = action('onClose');

const customActions = [
const trailingActions = [
{
iconType: 'gear',
onClick: () => action('Settings clicked')(),
'aria-label': 'Settings',
toolTipContent: 'Settings',
},
];

const StatefulFlyout = (props: Partial<FlyoutStoryArgs>) => {
const { onToggle, flyoutMenuDisplayMode, showCustomActions, ...rest } = props;
const { onToggle, flyoutMenuDisplayMode, showTrailingActions, ...rest } =
props;
const [_isOpen, setIsOpen] = useState(true);

const handleToggle = (open: boolean) => {
Expand All @@ -94,7 +96,7 @@ const StatefulFlyout = (props: Partial<FlyoutStoryArgs>) => {
<EuiFlyout
flyoutMenuDisplayMode={flyoutMenuDisplayMode}
flyoutMenuProps={{
customActions: showCustomActions ? customActions : undefined,
trailingActions: showTrailingActions ? trailingActions : undefined,
}}
{...rest}
onClose={() => {
Expand Down
39 changes: 39 additions & 0 deletions packages/eui/src/components/flyout/flyout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,45 @@ describe('EuiFlyout', () => {
expect(dialog).toHaveAttribute('aria-label', 'Test flyout');
expect(dialog).toHaveAttribute('data-managed-flyout-level', 'main');
});

it('only renders the history popover once more than two sessions are in history', () => {
const sharedKey = Symbol();
const Sessions = ({ count }: { count: number }) => (
<EuiFlyoutManager>
{Array.from({ length: count }, (_, index) => (
<EuiFlyout
key={index}
session="start"
historyKey={sharedKey}
onClose={() => {}}
flyoutMenuProps={{ title: `Session ${index + 1}` }}
aria-label={`Session ${index + 1}`}
>
Content
</EuiFlyout>
))}
</EuiFlyoutManager>
);

const { queryAllByTestSubject, rerender } = render(
<Sessions count={2} />
);

// Two sessions leaves a single history entry, which the back button
// already navigates to
expect(
queryAllByTestSubject('euiFlyoutMenuBackButton').length
).toBeGreaterThan(0);
expect(queryAllByTestSubject('euiFlyoutMenuHistoryButton')).toHaveLength(
0
);

rerender(<Sessions count={3} />);

expect(
queryAllByTestSubject('euiFlyoutMenuHistoryButton').length
).toBeGreaterThan(0);
});
});

describe('ref forwarding', () => {
Expand Down
Loading