Skip to content

Commit 67499dd

Browse files
authored
[Unified Tabs] Make props renderContent and getPreviewData optional (#240987)
## Summary Follow up to #239590. Fixes #241002. - Removes `disablePreview` prop and instead makes `getPreviewData` callback prop optional. If `getPreviewData` is not provided we render the tab without `TabPreview`. - Makes `renderContent` callback prop optional. If `renderContent` is not provided, we return the tabs without the flex group and content wrapper. This is for a use case where package users want to manage how and where to render tab content on their own. The use case for both updates is the usage of the unified tabs package in Lens (#235372). The defaults (both prop callbacks provided) are how Discover continues to work with unified tabs, no update needed. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels.
1 parent 64840f2 commit 67499dd

File tree

6 files changed

+138
-65
lines changed

6 files changed

+138
-65
lines changed

src/platform/packages/shared/kbn-unified-tabs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export {
1616
} from './src/components/tabbed_content';
1717
export { useNewTabProps } from './src/hooks/use_new_tab_props';
1818
export { getNextTabNumber } from './src/utils/get_next_tab_number';
19+
export { getTabIdAttribute } from './src/utils/get_tab_attributes';

src/platform/packages/shared/kbn-unified-tabs/src/components/tab/tab.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ describe('Tab', () => {
256256
expect(onLabelEdited).not.toHaveBeenCalled();
257257
});
258258

259-
it('shows preview when disablePreview is false', async () => {
259+
it('shows preview when getPreviewData is set', async () => {
260260
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
261261
const onLabelEdited = jest.fn();
262262
const onSelect = jest.fn();
@@ -273,7 +273,6 @@ describe('Tab', () => {
273273
onLabelEdited={onLabelEdited}
274274
onSelect={onSelect}
275275
onClose={onClose}
276-
disablePreview={false}
277276
/>
278277
);
279278

@@ -288,12 +287,14 @@ describe('Tab', () => {
288287
});
289288

290289
await waitFor(() => {
290+
const tabWithBackground = screen.getByTestId(`unifiedTabs_tab_${tabItem.id}`);
291+
expect(tabWithBackground).toBeInTheDocument();
291292
const preview = screen.getByTestId(`unifiedTabs_tabPreviewCodeBlock_${tabItem.id}`);
292293
expect(preview).toBeInTheDocument();
293294
});
294295
});
295296

296-
it('does not show preview when disablePreview is true', async () => {
297+
it('does not show preview when getPreviewData is not set', async () => {
297298
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
298299
const onLabelEdited = jest.fn();
299300
const onSelect = jest.fn();
@@ -306,11 +307,9 @@ describe('Tab', () => {
306307
item={tabItem}
307308
isSelected={false}
308309
services={servicesMock}
309-
getPreviewData={getPreviewDataMock}
310310
onLabelEdited={onLabelEdited}
311311
onSelect={onSelect}
312312
onClose={onClose}
313-
disablePreview={true}
314313
/>
315314
);
316315

@@ -325,6 +324,8 @@ describe('Tab', () => {
325324
});
326325

327326
await waitFor(() => {
327+
const tabWithBackground = screen.getByTestId(`unifiedTabs_tab_${tabItem.id}`);
328+
expect(tabWithBackground).toBeInTheDocument();
328329
const preview = screen.queryByTestId(`unifiedTabs_tabPreviewCodeBlock_${tabItem.id}`);
329330
expect(preview).not.toBeInTheDocument();
330331
});

src/platform/packages/shared/kbn-unified-tabs/src/components/tab/tab.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,14 @@ export interface TabProps {
4444
tabContentId: string;
4545
tabsSizeConfig: TabsSizeConfig;
4646
getTabMenuItems?: GetTabMenuItems;
47-
getPreviewData: (item: TabItem) => TabPreviewData;
47+
getPreviewData?: (item: TabItem) => TabPreviewData;
4848
services: TabsServices;
4949
onLabelEdited: EditTabLabelProps['onLabelEdited'];
5050
onSelect: (item: TabItem) => Promise<void>;
5151
onClose: ((item: TabItem) => Promise<void>) | undefined;
5252
onSelectedTabKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => Promise<void>;
5353
disableCloseButton?: boolean;
5454
disableInlineLabelEditing?: boolean;
55-
disablePreview?: boolean;
5655
disableDragAndDrop?: boolean;
5756
}
5857

@@ -82,7 +81,6 @@ export const Tab: React.FC<TabProps> = (props) => {
8281
onSelectedTabKeyDown,
8382
disableCloseButton = false,
8483
disableInlineLabelEditing = false,
85-
disablePreview = false,
8684
disableDragAndDrop = false,
8785
} = props;
8886
const { euiTheme } = useEuiTheme();
@@ -91,7 +89,7 @@ export const Tab: React.FC<TabProps> = (props) => {
9189
const [isInlineEditActive, setIsInlineEditActive] = useState<boolean>(false);
9290
const [showPreview, setShowPreview] = useState<boolean>(false);
9391
const [isActionPopoverOpen, setActionPopover] = useState<boolean>(false);
94-
const previewData = useMemo(() => getPreviewData(item), [getPreviewData, item]);
92+
const previewData = useMemo(() => getPreviewData?.(item), [getPreviewData, item]);
9593

9694
const hidePreview = useCallback(() => setShowPreview(false), [setShowPreview]);
9795

@@ -218,7 +216,7 @@ export const Tab: React.FC<TabProps> = (props) => {
218216
/>
219217
) : (
220218
<div css={getTabLabelContainerCss(euiTheme)} className="unifiedTabs__tabLabel">
221-
{previewData.status === TabStatus.RUNNING && (
219+
{previewData?.status === TabStatus.RUNNING && (
222220
<EuiProgress size="xs" color="accent" position="absolute" />
223221
)}
224222
<EuiFlexGroup
@@ -279,7 +277,6 @@ export const Tab: React.FC<TabProps> = (props) => {
279277
<EuiButtonIcon
280278
// semantically role="tablist" does not allow other buttons in tabs
281279
aria-hidden={true}
282-
tabIndex={-1}
283280
color="text"
284281
data-test-subj={`unifiedTabs_closeTabBtn_${item.id}`}
285282
iconType="cross"
@@ -294,22 +291,30 @@ export const Tab: React.FC<TabProps> = (props) => {
294291
</div>
295292
);
296293

294+
const tabWithBackground = (
295+
<TabWithBackground
296+
data-test-subj={`unifiedTabs_tab_${item.id}`}
297+
isSelected={isSelected}
298+
isDragging={isDragging}
299+
services={services}
300+
>
301+
{mainTabContent}
302+
</TabWithBackground>
303+
);
304+
305+
if (!previewData) {
306+
return tabWithBackground;
307+
}
308+
297309
return (
298310
<TabPreview
299-
showPreview={!disablePreview && showPreview}
311+
showPreview={showPreview}
300312
setShowPreview={setShowPreview}
301313
stopPreviewOnHover={isInlineEditActive || isActionPopoverOpen}
302314
tabItem={item}
303315
previewData={previewData}
304316
>
305-
<TabWithBackground
306-
data-test-subj={`unifiedTabs_tab_${item.id}`}
307-
isSelected={isSelected}
308-
isDragging={isDragging}
309-
services={services}
310-
>
311-
{mainTabContent}
312-
</TabWithBackground>
317+
{tabWithBackground}
313318
</TabPreview>
314319
);
315320
};

src/platform/packages/shared/kbn-unified-tabs/src/components/tabbed_content/tabbed_content.test.tsx

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import React, { useState } from 'react';
1111
import { render, screen, waitFor } from '@testing-library/react';
1212
import { userEvent } from '@testing-library/user-event';
13-
import { TabbedContent, type TabbedContentProps } from './tabbed_content';
13+
import type { TabbedContentProps } from './tabbed_content';
14+
import { TabbedContent } from './tabbed_content';
1415
import { getPreviewDataMock } from '../../../__mocks__/get_preview_data';
1516
import { servicesMock } from '../../../__mocks__/services';
1617

@@ -20,18 +21,18 @@ const NEW_TAB = {
2021
};
2122

2223
describe('TabbedContent', () => {
23-
const user = userEvent.setup();
24-
2524
const TabsWrapper = ({
2625
initialItems,
2726
initialSelectedItemId,
2827
onChanged,
2928
onEBTEvent,
29+
disableRenderContent = false,
3030
}: {
3131
initialItems: TabbedContentProps['items'];
3232
initialSelectedItemId?: TabbedContentProps['selectedItemId'];
3333
onChanged: TabbedContentProps['onChanged'];
3434
onEBTEvent: TabbedContentProps['onEBTEvent'];
35+
disableRenderContent?: boolean;
3536
}) => {
3637
const [{ managedItems, managedSelectedItemId }, setState] = useState<{
3738
managedItems: TabbedContentProps['items'];
@@ -57,9 +58,11 @@ describe('TabbedContent', () => {
5758
}}
5859
onEBTEvent={onEBTEvent}
5960
onClearRecentlyClosed={jest.fn()}
60-
renderContent={(item) => (
61-
<div style={{ paddingTop: '16px' }}>Content for tab: {item.label}</div>
62-
)}
61+
renderContent={
62+
!disableRenderContent
63+
? (item) => <div style={{ paddingTop: '16px' }}>Content for tab: {item.label}</div>
64+
: undefined
65+
}
6366
/>
6467
);
6568
};
@@ -198,6 +201,12 @@ describe('TabbedContent', () => {
198201
});
199202

200203
it('correctly numbers duplicate tabs when multiple copies exist', async () => {
204+
const user = userEvent.setup({
205+
delay: null,
206+
advanceTimers: jest.advanceTimersByTime,
207+
pointerEventsCheck: 0,
208+
});
209+
201210
const initialItems = [
202211
{ id: 'tab1', label: 'Tab 1' },
203212
{ id: 'tab2', label: 'Tab 1 (copy)' },
@@ -237,6 +246,8 @@ describe('TabbedContent', () => {
237246
});
238247

239248
it('correctly duplicates tabs with regex special characters in the label', async () => {
249+
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
250+
240251
const tabWithSpecialChars = { id: 'tab1', label: 'Tab (1+2)*.?' };
241252
const initialItems = [tabWithSpecialChars, { id: 'tab2', label: 'Regular Tab' }];
242253
const onChanged = jest.fn();
@@ -270,6 +281,8 @@ describe('TabbedContent', () => {
270281
});
271282

272283
it('can switch to a different tab and sends tabSwitched event', async () => {
284+
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
285+
273286
const initialItems = [
274287
{ id: 'tab1', label: 'Tab 1' },
275288
{ id: 'tab2', label: 'Tab 2' },
@@ -287,7 +300,7 @@ describe('TabbedContent', () => {
287300
/>
288301
);
289302

290-
await userEvent.click(screen.getByTestId(`unifiedTabs_selectTabBtn_${secondTab.id}`));
303+
await user.click(screen.getByTestId(`unifiedTabs_selectTabBtn_${secondTab.id}`));
291304
await waitFor(() => {
292305
expect(onEBTEvent).toHaveBeenCalledWith({
293306
eventName: 'tabSwitched',
@@ -303,6 +316,8 @@ describe('TabbedContent', () => {
303316
});
304317

305318
it('can close other tabs and sends tabClosedOthers event', async () => {
319+
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
320+
306321
const initialItems = [
307322
{ id: 'tab1', label: 'Tab 1' },
308323
{ id: 'tab2', label: 'Tab 2' },
@@ -347,6 +362,8 @@ describe('TabbedContent', () => {
347362
});
348363

349364
it('can close tabs to the right and sends tabClosedToTheRight event', async () => {
365+
const user = userEvent.setup({ delay: null, advanceTimers: jest.advanceTimersByTime });
366+
350367
const initialItems = [
351368
{ id: 'tab1', label: 'Tab 1' },
352369
{ id: 'tab2', label: 'Tab 2' },
@@ -387,4 +404,52 @@ describe('TabbedContent', () => {
387404
});
388405
});
389406
});
407+
408+
it('renders tab content when renderContent is provided', () => {
409+
const initialItems = [
410+
{ id: 'tab1', label: 'Tab 1' },
411+
{ id: 'tab2', label: 'Tab 2' },
412+
];
413+
const onChanged = jest.fn();
414+
const onEBTEvent = jest.fn();
415+
416+
render(
417+
<TabsWrapper
418+
initialItems={initialItems}
419+
initialSelectedItemId={initialItems[0].id}
420+
onChanged={onChanged}
421+
onEBTEvent={onEBTEvent}
422+
/>
423+
);
424+
425+
// The tabs bar should be rendered
426+
expect(screen.queryByTestId('unifiedTabs_tabsBar')).toBeInTheDocument();
427+
// When renderContent is provided, the tab content area should be rendered
428+
expect(screen.getByTestId('unifiedTabs_selectedTabContent')).toBeInTheDocument();
429+
expect(screen.getByText('Content for tab: Tab 1')).toBeInTheDocument();
430+
});
431+
432+
it('does not render tab content when renderContent is not provided', () => {
433+
const initialItems = [
434+
{ id: 'tab1', label: 'Tab 1' },
435+
{ id: 'tab2', label: 'Tab 2' },
436+
];
437+
const onChanged = jest.fn();
438+
const onEBTEvent = jest.fn();
439+
440+
render(
441+
<TabsWrapper
442+
initialItems={initialItems}
443+
initialSelectedItemId={initialItems[0].id}
444+
onChanged={onChanged}
445+
onEBTEvent={onEBTEvent}
446+
disableRenderContent={true}
447+
/>
448+
);
449+
450+
// The tabs bar should still be rendered
451+
expect(screen.queryByTestId('unifiedTabs_tabsBar')).toBeInTheDocument();
452+
// When renderContent is not provided, the tab content area should NOT be rendered
453+
expect(screen.queryByTestId('unifiedTabs_selectedTabContent')).not.toBeInTheDocument();
454+
});
390455
});

0 commit comments

Comments
 (0)