Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7efe9ad
Merge branch 'main' of github.com:elastic/kibana
drewdaemon Dec 5, 2025
d86f3cf
Merge branch 'main' of github.com:elastic/kibana
drewdaemon Dec 15, 2025
8cc36c7
Merge branch 'main' of github.com:elastic/kibana
drewdaemon Dec 18, 2025
cd88219
add preview
drewdaemon Dec 18, 2025
6f5e85e
position to the left
drewdaemon Dec 18, 2025
72359dc
passing query
drewdaemon Dec 18, 2025
7ee302e
get preview data and types working
drewdaemon Dec 19, 2025
751aef8
revert selector change
drewdaemon Dec 19, 2025
e640999
Merge branch 'main' of github.com:elastic/kibana into 244863/recently…
drewdaemon Dec 19, 2025
60a20c4
add test that preview shows up
drewdaemon Dec 19, 2025
041894c
fix position on small screens
drewdaemon Dec 19, 2025
1be1691
Merge branch 'main' of github.com:elastic/kibana into 244863/recently…
drewdaemon Jan 5, 2026
c54f5a2
Merge branch 'main' into 244863/recently-closed-tabs-preview
elasticmachine Jan 8, 2026
3a5dd80
add preview to open tabs
drewdaemon Jan 8, 2026
d730ca2
fix recently-closed check
drewdaemon Jan 8, 2026
76bf83f
remove title computation
drewdaemon Jan 8, 2026
56237e6
only update recently-closed items when menu opens
drewdaemon Jan 8, 2026
eb0ac4d
Merge branch 'main' into 244863/recently-closed-tabs-preview
elasticmachine Jan 12, 2026
7c576f6
Merge branch '244863/recently-closed-tabs-preview' of github.com:drew…
drewdaemon Jan 12, 2026
90e54cf
reposition preview
drewdaemon Jan 12, 2026
6b7b1d4
lowercase names
drewdaemon Jan 12, 2026
d898d61
use text truncator
drewdaemon Jan 12, 2026
07a4a5d
Merge branch 'main' into 244863/recently-closed-tabs-preview
elasticmachine Jan 12, 2026
ce07765
remove browser tooltip
drewdaemon Jan 13, 2026
a26ae69
more tooltip disabling
drewdaemon Jan 13, 2026
b5783c7
Merge branch '244863/recently-closed-tabs-preview' of github.com:drew…
drewdaemon Jan 13, 2026
1a8e7ca
fix selector
drewdaemon Jan 13, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TabbedContentTemplate: StoryFn<TabbedContentProps> = (args) => {
{...args}
items={managedItems}
selectedItemId={managedSelectedItemId}
recentlyClosedItems={[]}
recentlyClosedItems={args.recentlyClosedItems}
createItem={getNewTabDefaultProps}
getPreviewData={getPreviewDataMock}
services={servicesMock}
Expand Down Expand Up @@ -94,5 +94,12 @@ export const WithMultipleTabs: StoryObj<TabbedContentProps> = {
},
],
selectedItemId: '3',
recentlyClosedItems: [
{
id: '4',
label: 'Closed Tab',
closedAt: 123456789,
},
],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface TabPreviewProps {
previewData: TabPreviewData;
stopPreviewOnHover?: boolean;
previewDelay?: number;
position?: 'bottom' | 'left';
}

const getQueryLanguage = (tabPreviewData: TabPreviewData) => {
Expand All @@ -62,6 +63,7 @@ export const TabPreview: React.FC<TabPreviewProps> = ({
tabItem,
previewData,
stopPreviewOnHover,
position = 'bottom',
previewDelay = 1250, // as "long" EuiToolTip delay
}) => {
const { euiTheme } = useEuiTheme();
Expand All @@ -73,27 +75,42 @@ export const TabPreview: React.FC<TabPreviewProps> = ({
useEffect(() => {
if (showPreview && tabRef.current) {
const rect = tabRef.current.getBoundingClientRect();
const windowWidth = window.innerWidth;

// Check if preview would extend beyond right edge
const wouldExtendBeyondRight = rect.left + PREVIEW_WIDTH > windowWidth;
setTabPreviewData(previewData);

// Calculate left position based on screen edge constraints
let leftPosition = rect.left + window.scrollX;
if (position === 'left') {
// Position to the left of the element
let leftPosition = rect.left + window.scrollX - PREVIEW_WIDTH - euiTheme.base - 30; // extra 30 to push it off the EUI selectable menu
const topPosition = rect.top + window.scrollY - euiTheme.base / 2;

if (wouldExtendBeyondRight) {
// Align right edge of preview with right edge of window
leftPosition = windowWidth - PREVIEW_WIDTH + window.scrollX;
}
// Ensure preview doesn't go off left edge
if (leftPosition < window.scrollX) {
leftPosition = window.scrollX + euiTheme.base;
}

setTabPreviewData(previewData);
setTabPosition({
top: topPosition,
left: leftPosition,
});
} else {
// Position below the element (default)
let leftPosition = rect.left + window.scrollX;

setTabPosition({
top: rect.bottom + window.scrollY + euiTheme.base / 2,
left: leftPosition,
});
// Check if preview would extend beyond right edge
const wouldExtendBeyondRight = rect.left + PREVIEW_WIDTH > window.innerWidth;

if (wouldExtendBeyondRight) {
// Align right edge of preview with right edge of window
leftPosition = window.innerWidth - PREVIEW_WIDTH + window.scrollX;
}

setTabPosition({
top: rect.bottom + window.scrollY + euiTheme.base / 2,
left: leftPosition,
});
}
}
}, [showPreview, previewData, tabItem, euiTheme.base]);
}, [showPreview, previewData, tabItem, euiTheme.base, position]);

const onKeyDown = useCallback(
(event: KeyboardEvent) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ import {
closeOtherTabs,
closeTabsToTheRight,
} from '../../utils/manage_tabs';
import type { TabItem, TabsServices, TabPreviewData, TabsEBTEvent } from '../../types';
import type {
TabItem,
TabsServices,
TabPreviewData,
TabsEBTEvent,
RecentlyClosedTabItem,
} from '../../types';
import { TabsEventName } from '../../types';
import { getNextTabNumber } from '../../utils/get_next_tab_number';
import { MAX_ITEMS_COUNT, TAB_SWITCH_DEBOUNCE_MS } from '../../constants';
Expand All @@ -43,7 +49,7 @@ export interface TabbedContentProps
> {
items: TabItem[];
selectedItemId?: string;
recentlyClosedItems: TabItem[];
recentlyClosedItems: RecentlyClosedTabItem[];
'data-test-subj'?: string;
services: TabsServices;
hideTabsBar?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const items = Array.from({ length: 5 }).map((_, i) => ({
const recentlyClosedItems = Array.from({ length: 3 }).map((_, i) => ({
id: `closed-tab-${i}`,
label: `Closed Tab ${i}`,
closedAt: 0,
}));

const tabContentId = 'test-content-id';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
keys,
} from '@elastic/eui';
import { Tab, type TabProps } from '../tab';
import type { TabItem, TabsServices, TabsEBTEvent } from '../../types';
import type { TabItem, TabsServices, TabsEBTEvent, RecentlyClosedTabItem } from '../../types';
import { TabsEventName } from '../../types';
import { getTabIdAttribute } from '../../utils/get_tab_attributes';
import { useResponsiveTabs } from '../../hooks/use_responsive_tabs';
Expand Down Expand Up @@ -67,7 +67,7 @@ export type TabsBarProps = Pick<
> & {
items: TabItem[];
selectedItem: TabItem | null;
recentlyClosedItems: TabItem[];
recentlyClosedItems: RecentlyClosedTabItem[];
unsavedItemIds?: string[];
maxItemsCount?: number;
services: TabsServices;
Expand Down Expand Up @@ -385,6 +385,7 @@ export const TabsBar = forwardRef<TabsBarApi, TabsBarProps>(
items={items}
selectedItem={selectedItem}
recentlyClosedItems={recentlyClosedItems}
getPreviewData={getPreviewData}
onSelect={onSelect}
onSelectRecentlyClosed={onSelectRecentlyClosed}
onClearRecentlyClosed={onClearRecentlyClosed}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TabsBarMenu } from './tabs_bar_menu';
import type { TabItem } from '../../types';
import { TabStatus, type RecentlyClosedTabItem } from '../../types';

const mockTabs = [
{ id: 'tab1', label: 'Tab 1' },
{ id: 'tab2', label: 'Tab 2' },
{ id: 'tab3', label: 'Tab 3' },
];

const mockRecentlyClosedTabs = [
{ id: 'closed1', label: 'Closed Tab 1' },
{ id: 'closed2', label: 'Closed Tab 2' },
const mockRecentlyClosedTabs: RecentlyClosedTabItem[] = [
{ id: 'closed1', label: 'Closed Tab 1', closedAt: 0 },
{ id: 'closed2', label: 'Closed Tab 2', closedAt: 0 },
];

const tabsBarMenuButtonTestId = 'unifiedTabs_tabsBarMenuButton';
Expand Down Expand Up @@ -122,7 +124,9 @@ describe('TabsBarMenu', () => {
const menuButton = await screen.findByTestId(tabsBarMenuButtonTestId);
await user.click(menuButton);

const closedTabOption = (await screen.findAllByTitle(mockRecentlyClosedTabs[0].label))[0];
const closedTabOption = await screen.findByTestId(
`unifiedTabs_tabsMenu_recentlyClosedTab_${mockRecentlyClosedTabs[0].id}`
);
await user.click(closedTabOption);

expect(mockOnSelectClosedTab).toHaveBeenCalledWith(mockRecentlyClosedTabs[0]);
Expand Down Expand Up @@ -154,7 +158,7 @@ describe('TabsBarMenu', () => {
const menuButton = await screen.findByTestId(tabsBarMenuButtonTestId);
await user.click(menuButton);

const selectedTabOption = (await screen.findAllByTitle(mockTabs[0].label))[0];
const selectedTabOption = (await screen.findAllByText(mockTabs[0].label))[0];
expect(selectedTabOption.closest('[aria-selected="true"]')).toBeInTheDocument();
});

Expand All @@ -178,4 +182,34 @@ describe('TabsBarMenu', () => {
expect(await screen.findByText(/5 minutes ago/i)).toBeVisible();
expect(await screen.findByText(/10 minutes ago/i)).toBeVisible();
});

it('shows preview when callback is provided', async () => {
const user = userEvent.setup();
const now = Date.now();
const propsWithTimestamps = {
...defaultProps,
getPreviewData: (item: TabItem) => ({
title: `Preview of ${item.label}`,
query: { language: 'esql', query: 'SELECT * FROM table' },
status: TabStatus.DEFAULT,
}),
recentlyClosedItems: [
{ id: 'closed1', label: 'Closed Tab 1', closedAt: now - 5 * 60 * 1000 }, // 5 minutes
{ id: 'closed2', label: 'Closed Tab 2', closedAt: now - 10 * 60 * 1000 }, // 10 minutes
],
};

render(<TabsBarMenu {...propsWithTimestamps} />);

const menuButton = screen.getByTestId(tabsBarMenuButtonTestId);
await user.click(menuButton);

expect(await screen.findByText('Recently closed')).toBeVisible();

// Hover over the closed tab item
await user.hover(screen.getByText('Closed Tab 1'));

// Wait for the preview to appear
expect(await screen.findByText('Preview of Closed Tab 1')).toBeVisible();
});
});
Loading