mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Solution Side Nav] Misc UI fixes (#216109)
Part of https://github.com/elastic/kibana-team/issues/1439 Pulled from https://github.com/elastic/kibana/pull/210893 https://github.com/elastic/kibana/pull/215969 ## Summary 1. Allow item in the secondary panel to use the `renderItem` field 2. Fix handling of `defaultIsCollapsed` for items in the secondary panel 3. Allow secondary panel to contain a mix of ungrouped items as well as sub-groups of items  4. Fix the flagging of the "active" parent in the main nav panel, based on the current URL  ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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
This commit is contained in:
parent
369a43b2c2
commit
05a8703d48
6 changed files with 186 additions and 43 deletions
|
@ -500,5 +500,120 @@ describe('Panel', () => {
|
|||
expect(queryByTestId(/panelNavItem-id-item1/)).toBeVisible();
|
||||
expect(queryByTestId(/panelNavItem-id-item3/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('allows panel to contain a mix of ungrouped items and grouped items', () => {
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
title: 'Root',
|
||||
path: 'root',
|
||||
isCollapsible: false,
|
||||
children: [
|
||||
{
|
||||
id: 'group1',
|
||||
title: 'Group 1',
|
||||
path: 'root.group1',
|
||||
href: '/app/item1',
|
||||
renderAs: 'panelOpener',
|
||||
children: [
|
||||
{
|
||||
id: 'item0',
|
||||
title: 'Item 0',
|
||||
href: '/app/item0',
|
||||
path: 'root.group1.foo.item0',
|
||||
},
|
||||
{
|
||||
id: 'foo',
|
||||
title: 'Group 1',
|
||||
path: 'root.group1.foo',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
href: '/app/item1',
|
||||
path: 'root.group1.foo.item1',
|
||||
title: 'Item 1',
|
||||
},
|
||||
{
|
||||
id: 'item2',
|
||||
href: '/app/item2',
|
||||
path: 'root.group1.foo.item2',
|
||||
title: 'Item 2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { queryByTestId } = renderNavigation({
|
||||
navTreeDef: of(navTree),
|
||||
});
|
||||
|
||||
queryByTestId(/panelOpener-root.group1/)?.click(); // open the panel
|
||||
|
||||
expect(queryByTestId(/panelGroupId-foo/)).toBeVisible(); // no crash
|
||||
});
|
||||
|
||||
test('allows panel items to use custom rendering', () => {
|
||||
const componentSpy = jest.fn();
|
||||
|
||||
const Custom: React.FC = () => {
|
||||
componentSpy();
|
||||
return <>Hello</>;
|
||||
};
|
||||
|
||||
const navTree: NavigationTreeDefinitionUI = {
|
||||
id: 'es',
|
||||
body: [
|
||||
{
|
||||
id: 'root',
|
||||
title: 'Root',
|
||||
path: 'root',
|
||||
isCollapsible: false,
|
||||
children: [
|
||||
{
|
||||
id: 'group1',
|
||||
title: 'Group 1',
|
||||
path: 'root.group1',
|
||||
href: '/app/item1',
|
||||
renderAs: 'panelOpener',
|
||||
children: [
|
||||
{
|
||||
id: 'foo',
|
||||
title: 'Group 1',
|
||||
path: 'root.group1.foo',
|
||||
children: [
|
||||
{
|
||||
id: 'item1',
|
||||
title: 'Item 1',
|
||||
path: 'root.group1.foo.item1',
|
||||
renderItem: () => {
|
||||
return <Custom />;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { queryByTestId } = renderNavigation({
|
||||
navTreeDef: of(navTree),
|
||||
});
|
||||
|
||||
expect(componentSpy).not.toHaveBeenCalled();
|
||||
|
||||
queryByTestId(/panelOpener-root.group1/)?.click(); // open the panel
|
||||
|
||||
expect(componentSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -81,7 +81,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
|
|||
const isIconVisible = isNotMobile && !isSideNavCollapsed && !!children && children.length > 0;
|
||||
const hasLandingPage = Boolean(href);
|
||||
const isExpanded = selectedNode?.path === path;
|
||||
const isActive = hasLandingPage ? isActiveFromUrl(item.path, activeNodes) : isExpanded;
|
||||
const isActive = isActiveFromUrl(item.path, activeNodes) || isExpanded;
|
||||
|
||||
const itemClassNames = classNames(
|
||||
'sideNavItem',
|
||||
|
|
|
@ -14,10 +14,6 @@ import React, { Fragment, type FC } from 'react';
|
|||
import { PanelGroup } from './panel_group';
|
||||
import { PanelNavItem } from './panel_nav_item';
|
||||
|
||||
function isGroupNode({ children }: Pick<ChromeProjectNavigationNode, 'children'>) {
|
||||
return children !== undefined;
|
||||
}
|
||||
|
||||
function isItemNode({ children }: Pick<ChromeProjectNavigationNode, 'children'>) {
|
||||
return children === undefined;
|
||||
}
|
||||
|
@ -35,12 +31,12 @@ function isItemNode({ children }: Pick<ChromeProjectNavigationNode, 'children'>)
|
|||
function serializeChildren(node: PanelSelectedNode): ChromeProjectNavigationNode[] | undefined {
|
||||
if (!node.children) return undefined;
|
||||
|
||||
const allChildrenAreItems = node.children.every((_node) => {
|
||||
const someChildrenAreItems = node.children.some((_node) => {
|
||||
if (isItemNode(_node)) return true;
|
||||
return _node.renderAs === 'item';
|
||||
});
|
||||
|
||||
if (allChildrenAreItems) {
|
||||
if (someChildrenAreItems) {
|
||||
// Automatically wrap all the children into top level "root" group.
|
||||
return [
|
||||
{
|
||||
|
@ -52,17 +48,6 @@ function serializeChildren(node: PanelSelectedNode): ChromeProjectNavigationNode
|
|||
];
|
||||
}
|
||||
|
||||
const allChildrenAreGroups = node.children.every((_node) => {
|
||||
if (_node.renderAs === 'item') return false;
|
||||
return isGroupNode(_node);
|
||||
});
|
||||
|
||||
if (!allChildrenAreGroups) {
|
||||
throw new Error(
|
||||
`[Chrome navigation] Error in node [${node.id}]. Children must either all be "groups" or all "items" but not a mix of both.`
|
||||
);
|
||||
}
|
||||
|
||||
return node.children;
|
||||
}
|
||||
|
||||
|
|
|
@ -113,6 +113,7 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
|
|||
className={classNames.accordion}
|
||||
buttonClassName={accordionButtonClassName}
|
||||
data-test-subj={groupTestSubj}
|
||||
initialIsOpen={navNode.defaultIsCollapsed === false}
|
||||
buttonProps={{
|
||||
'data-test-subj': `panelAccordionBtnId-${navNode.id}`,
|
||||
}}
|
||||
|
|
|
@ -25,7 +25,7 @@ interface Props {
|
|||
export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
|
||||
const { navigateToUrl } = useServices();
|
||||
const { close: closePanel } = usePanel();
|
||||
const { id, icon, deepLink, openInNewTab } = item;
|
||||
const { id, icon, deepLink, openInNewTab, renderItem } = item;
|
||||
const href = deepLink?.url ?? item.href;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
|
@ -40,7 +40,9 @@ export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
|
|||
[closePanel, href, navigateToUrl]
|
||||
);
|
||||
|
||||
return (
|
||||
return renderItem ? (
|
||||
renderItem()
|
||||
) : (
|
||||
<EuiListGroupItem
|
||||
key={id}
|
||||
label={parentIsAccordion ? <SubItemTitle item={item} /> : item.title}
|
||||
|
|
|
@ -13,11 +13,15 @@ import { of } from 'rxjs';
|
|||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiCollapsibleNavBeta,
|
||||
EuiCollapsibleNavBetaProps,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHeader,
|
||||
EuiHeaderSection,
|
||||
EuiPageTemplate,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type {
|
||||
|
@ -128,28 +132,38 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
|
|||
icon: 'iInCircle',
|
||||
renderAs: 'panelOpener',
|
||||
children: [
|
||||
// FIXME: group mixed with items causes crash
|
||||
// {
|
||||
// id: 'sub1',
|
||||
// path: '',
|
||||
// title: 'Item 11',
|
||||
// href: '/app/kibana',
|
||||
// icon: 'iInCircle',
|
||||
// },
|
||||
// {
|
||||
// id: 'sub2',
|
||||
// path: '',
|
||||
// title: 'Item 12',
|
||||
// href: '/app/kibana',
|
||||
// icon: 'iInCircle',
|
||||
// },
|
||||
// {
|
||||
// id: 'sub3',
|
||||
// path: '',
|
||||
// title: 'Item 13',
|
||||
// href: '/app/kibana',
|
||||
// icon: 'iInCircle',
|
||||
// },
|
||||
{
|
||||
id: 'sub0',
|
||||
path: '',
|
||||
title: 'This text is not shown',
|
||||
renderItem: () => (
|
||||
<>
|
||||
<p>This panel contains a mix of ungrouped items and grouped items</p>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'sub1',
|
||||
path: '',
|
||||
title: 'Item 11',
|
||||
href: '/app/kibana',
|
||||
icon: 'iInCircle',
|
||||
},
|
||||
{
|
||||
id: 'sub2',
|
||||
path: '',
|
||||
title: 'Item 12',
|
||||
href: '/app/kibana',
|
||||
icon: 'iInCircle',
|
||||
},
|
||||
{
|
||||
id: 'sub3',
|
||||
path: '',
|
||||
title: 'Item 13',
|
||||
href: '/app/kibana',
|
||||
icon: 'iInCircle',
|
||||
},
|
||||
{
|
||||
id: 'child-section1',
|
||||
path: '',
|
||||
|
@ -240,6 +254,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
|
|||
path: '',
|
||||
icon: 'iInCircle',
|
||||
renderAs: 'accordion',
|
||||
defaultIsCollapsed: false,
|
||||
children: [
|
||||
{
|
||||
id: 'sub1',
|
||||
|
@ -272,7 +287,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
|
|||
},
|
||||
{
|
||||
id: 'item05',
|
||||
title: 'Item 05',
|
||||
title: 'Item 05, with custom',
|
||||
path: '',
|
||||
icon: 'iInCircle',
|
||||
renderAs: 'panelOpener',
|
||||
|
@ -284,6 +299,31 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
|
|||
href: '/app/kibana',
|
||||
icon: 'iInCircle',
|
||||
},
|
||||
{
|
||||
id: 'spacer1',
|
||||
path: '',
|
||||
title: 'This text is not shown.',
|
||||
renderItem: () => {
|
||||
return <EuiSpacer />;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'callout1',
|
||||
path: '',
|
||||
title: 'This text is not shown.',
|
||||
renderItem: () => {
|
||||
return (
|
||||
<EuiCallOut title="Check it out" iconType="cluster">
|
||||
<EuiFlexGroup justifyContent="spaceAround" direction="column">
|
||||
<EuiFlexItem>
|
||||
<p>Choose an integration to start</p>
|
||||
<EuiButton>Browse integrations</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue