From 78efb04c89e22233f7ae58b8314baa58610e25a0 Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 30 Jul 2026 12:20:15 -0700 Subject: [PATCH 01/27] plan for flyout menu action follow-up MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update [`packages/eui/src/components/flyout/flyout_menu.tsx`](packages/eui/src/components/flyout/flyout_menu.tsx) with a shared `EuiFlyoutMenuAction` type supporting EUI-standard `toolTipContent` and `toolTipProps`, plus `leadingActions` and `trailingActions` props. - Keep `customActions` as a deprecated compatibility alias for `trailingActions`; prefer `trailingActions` if both are supplied. Export the new type from [`packages/eui/src/components/flyout/index.ts`](packages/eui/src/components/flyout/index.ts). - Refactor action rendering so both slots preserve required accessible labels/click behavior and wrap buttons in `EuiToolTip` only when tooltip content is supplied. - Build an inline-start cluster that renders pagination or back/history first, then `leadingActions`, with logical spacing in [`packages/eui/src/components/flyout/flyout_menu.styles.ts`](packages/eui/src/components/flyout/flyout_menu.styles.ts). This matches the pagination PNG and also allows leading actions without pagination. - Change the history trigger to `clockCounter` and add a translatable “Recently visited” tooltip, while retaining its accessible history label and popover behavior. - Extend [`packages/eui/src/components/flyout/use_flyout_menu.ts`](packages/eui/src/components/flyout/use_flyout_menu.ts) so either new action slot counts as meaningful content in automatic display mode. - Use “control-group divider” in prose and a local `euiFlyoutMenu__divider` style in code. EUI consistently calls these compact vertical lines “dividers”; “rule” is reserved for the horizontal `EuiHorizontalRule`, and there is no reusable `EuiVerticalRule` component or shared vertical-divider mixin. - Model the styling on the private `dividerStyles` helper in [`packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts`](packages/eui/src/components/form/form_control_layout/form_control_layout.styles.ts): use a centered, shortened `::before` pseudo-element rather than a full-height border on the action container. Use logical properties, `block-size: ${euiTheme.size.l}`, `pointer-events: none`, and `border-inline-start: ${euiTheme.border.width.thin} solid ${euiTheme.border.color}`. - Treat the form-control helper as a pattern, not a direct dependency. The explicit short divider in [`packages/eui/src/components/markdown_editor/markdown_editor_toolbar.styles.ts`](packages/eui/src/components/markdown_editor/markdown_editor_toolbar.styles.ts) provides the toolbar sizing/spacing precedent, while [`packages/eui/src/components/button/split_button/split_button.styles.ts`](packages/eui/src/components/button/split_button/split_button.styles.ts) confirms that a border should be used instead of a background so the divider remains visible in Windows high-contrast mode. - Add dividers only when both adjacent groups exist: - Between built-in leading controls (pagination or back/history) and `leadingActions`. - Between effective trailing actions (`trailingActions` or deprecated `customActions`) and the built-in close control, only when the close control is visible. - Attach the pseudo-element to a conditional boundary style or wrapper rather than adding separator semantics to the DOM. For the trailing boundary, account for the absolutely positioned close button by applying the divider modifier to the existing close spacer/action boundary and preserving enough inline spacing for the close hit area. - Keep all placement RTL-safe with `inset-inline-*`, `margin-inline-*`, and `border-inline-*`; do not use physical left/right properties. If implementation constraints require a real divider element instead of a pseudo-element, follow the split-button precedent and mark it `aria-hidden="true"` rather than giving a decorative line `role="separator"`. From 5f38af69e84d9d9290361c605482c5c0ade1b50c Mon Sep 17 00:00:00 2001 From: Timothy Sullivan Date: Thu, 30 Jul 2026 14:03:19 -0700 Subject: [PATCH 02/27] support for leading and trailing actions / control-group dividers --- packages/eui/changelogs/upcoming/9856.md | 9 + .../src/components/flyout/flyout.stories.tsx | 14 +- .../components/flyout/flyout_menu.stories.tsx | 105 +++++- .../components/flyout/flyout_menu.styles.ts | 13 + .../components/flyout/flyout_menu.test.tsx | 323 +++++++++++++++++- .../eui/src/components/flyout/flyout_menu.tsx | 189 ++++++++-- packages/eui/src/components/flyout/index.ts | 1 + .../flyout/manager/flyout_managed.test.tsx | 30 ++ .../components/flyout/use_flyout_menu.test.ts | 28 ++ .../src/components/flyout/use_flyout_menu.ts | 5 +- .../containers/flyout/_flyout_menu.mdx | 9 +- .../flyout/_flyout_menu_pagination.mdx | 12 + 12 files changed, 682 insertions(+), 56 deletions(-) create mode 100644 packages/eui/changelogs/upcoming/9856.md diff --git a/packages/eui/changelogs/upcoming/9856.md b/packages/eui/changelogs/upcoming/9856.md new file mode 100644 index 000000000000..8be0bf3917b6 --- /dev/null +++ b/packages/eui/changelogs/upcoming/9856.md @@ -0,0 +1,9 @@ +- 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 + +**Deprecations** + +- Deprecated `customActions` in `EuiFlyoutMenu`; use `trailingActions` instead diff --git a/packages/eui/src/components/flyout/flyout.stories.tsx b/packages/eui/src/components/flyout/flyout.stories.tsx index 733bcaef3231..fb35ae97142d 100644 --- a/packages/eui/src/components/flyout/flyout.stories.tsx +++ b/packages/eui/src/components/flyout/flyout.stories.tsx @@ -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 = { @@ -38,7 +38,7 @@ const meta: Meta = { control: { type: 'radio' }, description: 'The display mode of the flyout menu.', }, - showCustomActions: { control: 'boolean' }, + showTrailingActions: { control: 'boolean' }, }, args: { // Component defaults @@ -53,7 +53,7 @@ const meta: Meta = { hideCloseButton: false, ownFocus: true, flyoutMenuDisplayMode: DEFAULT_MENU_DISPLAY_MODE, - showCustomActions: true, + showTrailingActions: true, }, parameters: { vrt: { @@ -68,16 +68,18 @@ type Story = StoryObj; const onClose = action('onClose'); -const customActions = [ +const trailingActions = [ { iconType: 'gear', onClick: () => action('Settings clicked')(), 'aria-label': 'Settings', + toolTipContent: 'Settings', }, ]; const StatefulFlyout = (props: Partial) => { - const { onToggle, flyoutMenuDisplayMode, showCustomActions, ...rest } = props; + const { onToggle, flyoutMenuDisplayMode, showTrailingActions, ...rest } = + props; const [_isOpen, setIsOpen] = useState(true); const handleToggle = (open: boolean) => { @@ -94,7 +96,7 @@ const StatefulFlyout = (props: Partial) => { { diff --git a/packages/eui/src/components/flyout/flyout_menu.stories.tsx b/packages/eui/src/components/flyout/flyout_menu.stories.tsx index 16dda5fb0873..8b3e4c21d280 100644 --- a/packages/eui/src/components/flyout/flyout_menu.stories.tsx +++ b/packages/eui/src/components/flyout/flyout_menu.stories.tsx @@ -20,26 +20,43 @@ import { EuiFlyoutMenu, EuiFlyoutMenuProps } from './flyout_menu'; import { EuiFlyoutHeader } from './flyout_header'; interface Args extends EuiFlyoutMenuProps { - showCustomActions: boolean; + leadingActionCount: number; + trailingActionCount: number; showHistoryItems: boolean; } +const ACTION_COUNT_CONTROL = { + control: { type: 'range' as const, min: 0, max: 4, step: 1 }, +}; + const meta: Meta = { title: 'Layout/EuiFlyout/EuiFlyoutMenu', component: EuiFlyoutMenu, argTypes: { hideTitle: { control: 'boolean' }, showBackButton: { control: 'boolean' }, - showCustomActions: { control: 'boolean' }, + leadingActionCount: { + ...ACTION_COUNT_CONTROL, + description: + 'Story-only control for the number of `leadingActions` to render.', + }, + trailingActionCount: { + ...ACTION_COUNT_CONTROL, + description: + 'Story-only control for the number of `trailingActions` to render.', + }, 'aria-label': { table: { disable: true } }, backButtonProps: { table: { disable: true } }, + trailingActions: { table: { disable: true } }, customActions: { table: { disable: true } }, + leadingActions: { table: { disable: true } }, historyItems: { table: { disable: true } }, }, args: { hideCloseButton: false, showBackButton: true, - showCustomActions: true, + leadingActionCount: 1, + trailingActionCount: 2, showHistoryItems: true, hideTitle: true, }, @@ -47,12 +64,41 @@ const meta: Meta = { export default meta; +const LEADING_ACTION_POOL = [ + { iconType: 'documents', label: 'View surrounding documents' }, + { iconType: 'pin', label: 'Pin' }, + { iconType: 'tag', label: 'Tag' }, + { iconType: 'download', label: 'Download' }, +]; + +const TRAILING_ACTION_POOL = [ + { iconType: 'minimize', label: 'Minimize' }, + { iconType: 'gear', label: 'Settings' }, + { iconType: 'broom', label: 'Clear' }, + { iconType: 'share', label: 'Share' }, +]; + +const buildActions = ( + pool: typeof LEADING_ACTION_POOL, + count: number, + actionName: string +) => + pool.slice(0, count).map(({ iconType, label }) => ({ + iconType, + onClick: () => { + action(actionName)(`${label} clicked`); + }, + 'aria-label': label, + toolTipContent: label, + })); + const MenuBarFlyout = (args: Args) => { const { hideTitle, hideCloseButton, showBackButton, - showCustomActions, + leadingActionCount, + trailingActionCount, showHistoryItems, } = args; @@ -77,13 +123,16 @@ const MenuBarFlyout = (args: Args) => { })) : undefined; - const customActions = ['gear', 'broom'].map((iconType) => ({ - iconType, - onClick: () => { - action('custom action')(`${iconType} action clicked`); - }, - 'aria-label': `${iconType} action`, - })); + const leadingActions = buildActions( + LEADING_ACTION_POOL, + leadingActionCount, + 'leading action' + ); + const trailingActions = buildActions( + TRAILING_ACTION_POOL, + trailingActionCount, + 'trailing action' + ); const titleId = 'menu-bar-example-main-title'; @@ -110,7 +159,8 @@ const MenuBarFlyout = (args: Args) => { showBackButton, backButtonProps, historyItems, - customActions: showCustomActions ? customActions : undefined, + leadingActions, + trailingActions, }} > {hideTitle && ( @@ -165,12 +215,26 @@ const PAGINATION_ITEMS = [ }, ]; -const PaginationFlyout = () => { +const PaginationFlyout = ({ + leadingActionCount, + trailingActionCount, +}: Pick) => { const [isFlyoutOpen, setIsFlyoutOpen] = useState(true); const [currentIndex, setCurrentIndex] = useState(0); const total = PAGINATION_ITEMS.length; const item = PAGINATION_ITEMS[currentIndex]; + const leadingActions = buildActions( + LEADING_ACTION_POOL, + leadingActionCount, + 'leading action' + ); + const trailingActions = buildActions( + TRAILING_ACTION_POOL, + trailingActionCount, + 'trailing action' + ); + return ( <> setIsFlyoutOpen(true)} disabled={isFlyoutOpen}> @@ -190,6 +254,8 @@ const PaginationFlyout = () => { onPrevious: () => setCurrentIndex((i) => Math.max(0, i - 1)), onNext: () => setCurrentIndex((i) => Math.min(total - 1, i + 1)), }, + leadingActions, + trailingActions, }} > @@ -208,10 +274,19 @@ const PaginationFlyout = () => { ); }; -export const PaginationExample: StoryObj = { +export const PaginationExample: StoryObj = { name: 'Pagination (prop-based)', parameters: { vrt: { selector: VRT_SELECTORS.portal }, }, - render: () => , + args: { + leadingActionCount: 1, + trailingActionCount: 0, + }, + render: ({ leadingActionCount, trailingActionCount }) => ( + + ), }; diff --git a/packages/eui/src/components/flyout/flyout_menu.styles.ts b/packages/eui/src/components/flyout/flyout_menu.styles.ts index e04b382fde74..874b4377e4fd 100644 --- a/packages/eui/src/components/flyout/flyout_menu.styles.ts +++ b/packages/eui/src/components/flyout/flyout_menu.styles.ts @@ -39,5 +39,18 @@ export const euiFlyoutMenuStyles = (euiThemeContext: UseEuiTheme) => { color: ${euiTheme.colors.textSubdued}; white-space: nowrap; `, + // Compact vertical line separating control groups in the menu bar, e.g. + // built-in controls from leading/trailing actions. Uses a border rather + // than a background so the line stays visible in Windows high contrast + // themes. `align-self` keeps the shortened line centered regardless of the + // containing flex group's alignment. + euiFlyoutMenu__divider: css` + align-self: center; + inline-size: 0; + block-size: ${euiTheme.size.l}; + margin-inline: ${euiTheme.size.xs}; + border-inline-start: ${euiTheme.border.thin}; + pointer-events: none; + `, }; }; diff --git a/packages/eui/src/components/flyout/flyout_menu.test.tsx b/packages/eui/src/components/flyout/flyout_menu.test.tsx index 47f2922fa1ea..5339871d505c 100644 --- a/packages/eui/src/components/flyout/flyout_menu.test.tsx +++ b/packages/eui/src/components/flyout/flyout_menu.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { render, screen, waitForEuiPopoverOpen } from '../../test/rtl'; import { requiredProps } from '../../test'; @@ -232,6 +232,35 @@ describe('EuiFlyoutMenu', () => { secondItem.querySelector('.euiListGroupItem__icon') ).not.toBeInTheDocument(); }); + + it('uses the clockCounter icon for the history trigger', async () => { + const { container } = renderWithContext( + + ); + + const historyButton = container.querySelector('[aria-label="History"]'); + await waitFor(() => { + expect( + historyButton?.querySelector('[data-euiicon-type="clockCounter"]') + ).toBeInTheDocument(); + }); + }); + + it('shows a "Recently visited" tooltip on hover of the history trigger', () => { + const { getByTestSubject, getByRole } = renderWithContext( + + ); + + fireEvent.mouseOver(getByTestSubject('euiFlyoutMenuHistoryButton')); + + expect(getByRole('tooltip')).toHaveTextContent('Recently visited'); + }); }); describe('custom actions', () => { @@ -278,6 +307,286 @@ describe('EuiFlyoutMenu', () => { }); }); + describe('leadingActions and trailingActions', () => { + const leadingActions = [ + { + iconType: 'documents', + onClick: jest.fn(), + 'aria-label': 'View surrounding documents', + }, + ]; + const trailingActions = [ + { + iconType: 'minimize', + onClick: jest.fn(), + 'aria-label': 'Minimize', + toolTipContent: 'Minimize', + }, + ]; + + it('renders leadingActions and calls their onClick handlers', () => { + const { container } = renderWithContext( + + ); + + const button = container.querySelector( + '[aria-label="View surrounding documents"]' + ); + expect(button).toBeInTheDocument(); + + button?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + expect(leadingActions[0].onClick).toHaveBeenCalledTimes(1); + }); + + it('renders trailingActions and calls their onClick handlers', () => { + const { container } = renderWithContext( + + ); + + const button = container.querySelector('[aria-label="Minimize"]'); + expect(button).toBeInTheDocument(); + + button?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + expect(trailingActions[0].onClick).toHaveBeenCalledTimes(1); + }); + + it('renders both leadingActions and trailingActions together', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelector('[aria-label="View surrounding documents"]') + ).toBeInTheDocument(); + expect( + container.querySelector('[aria-label="Minimize"]') + ).toBeInTheDocument(); + }); + + it('shows a tooltip when toolTipContent is provided', () => { + const { getByRole } = renderWithContext( + + ); + + const button = screen.getByLabelText('Minimize'); + fireEvent.mouseOver(button); + + expect(getByRole('tooltip')).toHaveTextContent('Minimize'); + }); + + it('does not render a tooltip when toolTipContent is not provided', () => { + const { queryByRole } = renderWithContext( + + ); + + const button = screen.getByLabelText('View surrounding documents'); + fireEvent.mouseOver(button); + + expect(queryByRole('tooltip')).not.toBeInTheDocument(); + }); + + it('forwards toolTipProps to the underlying EuiToolTip', () => { + const actionsWithToolTipProps = [ + { + iconType: 'minimize', + onClick: jest.fn(), + 'aria-label': 'Minimize', + toolTipContent: 'Minimize', + toolTipProps: { position: 'left' as const }, + }, + ]; + + const { getByRole } = renderWithContext( + + ); + + fireEvent.mouseOver(screen.getByLabelText('Minimize')); + + expect(getByRole('tooltip').className).toContain('euiToolTip-left'); + }); + + it('prefers trailingActions over the deprecated customActions alias when both are supplied', () => { + const deprecatedCustomActions = [ + { iconType: 'gear', onClick: jest.fn(), 'aria-label': 'Settings' }, + ]; + + const { container } = renderWithContext( + + ); + + expect( + container.querySelector('[aria-label="Minimize"]') + ).toBeInTheDocument(); + expect( + container.querySelector('[aria-label="Settings"]') + ).not.toBeInTheDocument(); + }); + + it('falls back to the deprecated customActions alias when trailingActions is not supplied', () => { + const deprecatedCustomActions = [ + { iconType: 'gear', onClick: jest.fn(), 'aria-label': 'Settings' }, + ]; + + const { container } = renderWithContext( + + ); + + expect( + container.querySelector('[aria-label="Settings"]') + ).toBeInTheDocument(); + }); + }); + + describe('control-group dividers', () => { + const leadingActions = [ + { + iconType: 'documents', + onClick: jest.fn(), + 'aria-label': 'View surrounding documents', + }, + ]; + const trailingActions = [ + { iconType: 'minimize', onClick: jest.fn(), 'aria-label': 'Minimize' }, + ]; + const historyItems = [{ title: 'History 1', onClick: jest.fn() }]; + + it('renders a divider between the back button and history when both are present', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(1); + }); + + it('does not render a divider when only the back button is present', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(0); + }); + + it('does not render a divider when only history is present', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(0); + }); + + it('renders a divider between built-in leading controls and leadingActions', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(1); + }); + + it('does not render a divider when leadingActions is present without built-in leading controls', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(0); + }); + + it('renders a divider between trailingActions and the close button', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(1); + }); + + it('does not render a trailing divider when the close button is hidden', () => { + const { container } = renderWithContext( + + ); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(0); + }); + + it('does not render a trailing divider when trailingActions is empty', () => { + const { container } = renderWithContext(); + + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(0); + }); + + it('renders both leading and trailing dividers independently when leadingActions is absent but trailing content exists', () => { + const { container } = renderWithContext( + + ); + + // Only the trailing/close divider should render since leadingActions is absent + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(1); + }); + + it('renders both boundary dividers when all groups are present', () => { + const { container } = renderWithContext( + + ); + + // back|history + built-in|leading + trailing|close + expect( + container.querySelectorAll('.euiFlyoutMenu__divider') + ).toHaveLength(3); + }); + }); + describe('pagination', () => { const pagination = { currentIndex: 1, @@ -353,6 +662,18 @@ describe('EuiFlyoutMenu', () => { expect(pagination.onNext).toHaveBeenCalledTimes(1); }); + it('disables screen reader output for the Prev/Next tooltips', () => { + const { getByTestSubject } = renderWithContext( + + ); + + fireEvent.mouseOver(getByTestSubject('euiFlyoutMenuPaginationNext')); + + expect( + getByTestSubject('euiFlyoutMenuPaginationNext') + ).not.toHaveAttribute('aria-describedby'); + }); + it('hides the back button when pagination is provided (pagination replaces navigation per design spec)', () => { const { queryByText } = renderWithContext( diff --git a/packages/eui/src/components/flyout/flyout_menu.tsx b/packages/eui/src/components/flyout/flyout_menu.tsx index 10bdf736bc66..81998f0c2093 100644 --- a/packages/eui/src/components/flyout/flyout_menu.tsx +++ b/packages/eui/src/components/flyout/flyout_menu.tsx @@ -23,7 +23,7 @@ import { EuiPopover } from '../popover'; import { EuiScreenReaderLive } from '../accessibility'; import { EuiText } from '../text'; import { EuiTitle } from '../title'; -import { EuiToolTip } from '../tool_tip'; +import { EuiToolTip, EuiToolTipProps } from '../tool_tip'; import { EuiFlyoutCloseButton } from './_flyout_close_button'; import { euiFlyoutMenuStyles } from './flyout_menu.styles'; import { EuiFlyoutMenuContext } from './flyout_menu_context'; @@ -78,6 +78,7 @@ export interface EuiFlyoutMenuPagination { /** * Custom action item for the flyout menu component + * @deprecated Use `EuiFlyoutMenuAction` with the `trailingActions` prop instead. */ export interface EuiFlyoutMenuCustomAction { /** @@ -94,6 +95,33 @@ export interface EuiFlyoutMenuCustomAction { 'aria-label': string; } +/** + * An action item for the `leadingActions` or `trailingActions` slots of the flyout menu. + */ +export interface EuiFlyoutMenuAction { + /** + * Icon type for the action button + */ + iconType: IconType; + /** + * onClick handler for the action button + */ + onClick: () => void; + /** + * Aria label for the action button + */ + 'aria-label': string; + /** + * Optional tooltip content shown on hover/focus of the action button + */ + toolTipContent?: EuiToolTipProps['content']; + /** + * Optional props to pass to the underlying **[EuiToolTip](/#/display/tooltip)**. + * Only used when `toolTipContent` is also provided. + */ + toolTipProps?: Partial>; +} + /** * Props for EuiFlyoutMenu */ @@ -145,8 +173,19 @@ export type EuiFlyoutMenuProps = CommonProps & * List of history items for the history popover */ historyItems?: EuiFlyoutHistoryItem[]; + /** + * List of action items rendered at the start (inline-start) of the menu bar, + * after any built-in leading controls (pagination, or back/history). + */ + leadingActions?: EuiFlyoutMenuAction[]; + /** + * List of action items rendered at the end (inline-end) of the menu bar, + * before the close button. + */ + trailingActions?: EuiFlyoutMenuAction[]; /** * List of custom action items for the menu component + * @deprecated Use `trailingActions` instead. If both are supplied, `trailingActions` takes precedence. */ customActions?: EuiFlyoutMenuCustomAction[]; /** @@ -178,15 +217,23 @@ const HistoryPopover: React.FC<{ setIsPopoverOpen(!isPopoverOpen); }; + const historyLabel = useEuiI18n('euiFlyoutMenu.history', 'History'); + const recentlyVisitedLabel = useEuiI18n( + 'euiFlyoutMenu.history.tooltip', + 'Recently visited' + ); + return ( + + + } isOpen={isPopoverOpen} onClick={handlePopoverButtonClick} @@ -216,8 +263,8 @@ const HistoryPopover: React.FC<{ const PaginationControls: React.FC<{ pagination: EuiFlyoutMenuPagination; - styles: ReturnType; -}> = ({ pagination, styles }) => { +}> = ({ pagination }) => { + const styles = useEuiMemoizedStyles(euiFlyoutMenuStyles); const { currentIndex, total, onPrevious, onNext } = pagination; const prevLabel = useEuiI18n('euiFlyoutMenu.pagination.previous', 'Previous'); const nextLabel = useEuiI18n('euiFlyoutMenu.pagination.next', 'Next'); @@ -290,6 +337,58 @@ const PaginationControls: React.FC<{ ); }; +const MenuDivider: React.FC = () => { + const styles = useEuiMemoizedStyles(euiFlyoutMenuStyles); + + return ( +