Skip to content

Commit 0d84936

Browse files
[SolutionSideNav] Add badge to all items except section header (#217301)
## Summary This PR adds the ability add badge to all side nav items, except section headers. Follow-up on #214854 ![Screenshot 2025-04-07 at 12 24 29](https://github.com/user-attachments/assets/9ae2a610-1e56-4853-8214-ecb417bd4855)
1 parent 9342cff commit 0d84936

File tree

5 files changed

+51
-35
lines changed

5 files changed

+51
-35
lines changed

src/platform/packages/shared/shared-ux/chrome/navigation/src/ui/components/navigation_item_open_panel.tsx

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
EuiButton,
2525
} from '@elastic/eui';
2626
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
27+
import { SubItemTitle } from './subitem_title';
2728
import { useNavigation as useServices } from '../../services';
2829
import { isActiveFromUrl } from '../../utils';
2930
import type { NavigateToUrlFn } from '../../types';
@@ -46,7 +47,11 @@ const getStyles = (euiTheme: EuiThemeComputed<{}>) => css`
4647
}
4748
`;
4849

49-
const getButtonStyles = (euiTheme: EuiThemeComputed<{}>, isActive: boolean) => css`
50+
const getButtonStyles = (
51+
euiTheme: EuiThemeComputed<{}>,
52+
isActive: boolean,
53+
withBadge?: boolean
54+
) => css`
5055
background-color: ${isActive ? transparentize(euiTheme.colors.lightShade, 0.5) : 'transparent'};
5156
transform: none !important; /* don't translateY 1px */
5257
color: inherit;
@@ -56,14 +61,21 @@ const getButtonStyles = (euiTheme: EuiThemeComputed<{}>, isActive: boolean) => c
5661
justify-content: flex-start;
5762
position: relative;
5863
}
59-
& .euiIcon {
60-
position: absolute;
61-
right: 0;
62-
top: 0;
63-
transform: translateY(50%);
64-
}
64+
${!withBadge
65+
? `
66+
& .euiIcon {
67+
position: absolute;
68+
right: 0;
69+
top: 0;
70+
transform: translateY(50%);
71+
}
72+
`
73+
: `
74+
& .euiBetaBadge {
75+
margin-left: -${euiTheme.size.m};
76+
}
77+
`}
6578
`;
66-
6779
interface Props {
6880
item: ChromeProjectNavigationNode;
6981
navigateToUrl: NavigateToUrlFn;
@@ -74,7 +86,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
7486
const { euiTheme } = useEuiTheme();
7587
const { open: openPanel, close: closePanel, selectedNode } = usePanel();
7688
const { isSideNavCollapsed } = useServices();
77-
const { title, deepLink, children } = item;
89+
const { title, deepLink, children, withBadge } = item;
7890
const { id, path } = item;
7991
const href = deepLink?.url ?? item.href;
8092
const isNotMobile = useIsWithinMinBreakpoint('s');
@@ -89,7 +101,10 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
89101
getStyles(euiTheme)
90102
);
91103

92-
const buttonClassNames = classNames('sideNavItem', getButtonStyles(euiTheme, isActive));
104+
const buttonClassNames = classNames(
105+
'sideNavItem',
106+
getButtonStyles(euiTheme, isActive, withBadge)
107+
);
93108

94109
const dataTestSubj = classNames(`nav-item`, `nav-item-${path}`, {
95110
[`nav-item-deepLinkId-${deepLink?.id}`]: !!deepLink,
@@ -144,17 +159,17 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
144159
className={buttonClassNames}
145160
data-test-subj={dataTestSubj}
146161
>
147-
{title}
162+
{withBadge ? <SubItemTitle item={item} /> : title}
148163
</EuiButton>
149164
);
150165
}
151166

152167
return (
153168
<EuiFlexGroup alignItems="center" gutterSize="xs">
154-
<EuiFlexItem style={{ flexBasis: isIconVisible ? '80%' : '100%' }}>
169+
<EuiFlexItem css={{ flexBasis: isIconVisible ? '80%' : '100%' }}>
155170
<EuiListGroup gutterSize="none">
156171
<EuiListGroupItem
157-
label={title}
172+
label={withBadge ? <SubItemTitle item={item} /> : title}
158173
href={href}
159174
wrapText
160175
onClick={onLinkClick}
@@ -166,7 +181,7 @@ export const NavigationItemOpenPanel: FC<Props> = ({ item, navigateToUrl, active
166181
</EuiListGroup>
167182
</EuiFlexItem>
168183
{isIconVisible && (
169-
<EuiFlexItem grow={0} style={{ flexBasis: '15%' }}>
184+
<EuiFlexItem grow={0} css={{ flexBasis: '15%' }}>
170185
<EuiButtonIcon
171186
display={isExpanded ? 'base' : 'empty'}
172187
size="s"

src/platform/packages/shared/shared-ux/chrome/navigation/src/ui/components/navigation_section_ui.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ function nodeToEuiCollapsibleNavProps(
421421
onClick,
422422
icon: navNode.icon,
423423
// @ts-expect-error title accepts JSX elements and they render correctly but the type definition expects a string
424-
title: !subItems && navNode.withBadge ? <SubItemTitle item={navNode} /> : navNode.title,
424+
title: navNode.withBadge ? <SubItemTitle item={navNode} /> : navNode.title,
425425
['data-test-subj']: dataTestSubj,
426426
iconProps: { size: deps.treeDepth === 0 ? 'm' : 's' },
427427

src/platform/packages/shared/shared-ux/chrome/navigation/src/ui/components/panel/panel_group.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { css } from '@emotion/css';
2323

2424
import type { ChromeProjectNavigationNode } from '@kbn/core-chrome-browser';
25+
import { SubItemTitle } from '../subitem_title';
2526
import { PanelNavItem } from './panel_nav_item';
2627

2728
const accordionButtonClassName = 'sideNavPanelAccordion__button';
@@ -64,7 +65,7 @@ interface Props {
6465

6566
export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRuleBefore }) => {
6667
const { euiTheme } = useEuiTheme();
67-
const { id, title, appendHorizontalRule, spaceBefore: _spaceBefore } = navNode;
68+
const { id, title, appendHorizontalRule, spaceBefore: _spaceBefore, withBadge } = navNode;
6869
const filteredChildren = navNode.children?.filter((child) => child.sideNavStatus !== 'hidden');
6970
const classNames = getClassnames(euiTheme);
7071
const hasTitle = !!title && title !== '';
@@ -83,21 +84,18 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
8384
}
8485
}
8586

86-
const renderChildren = useCallback(
87-
({ parentIsAccordion } = { parentIsAccordion: false }) => {
88-
if (!filteredChildren) return null;
87+
const renderChildren = useCallback(() => {
88+
if (!filteredChildren) return null;
8989

90-
return filteredChildren.map((item, i) => {
91-
const isItem = item.renderAs === 'item' || !item.children;
92-
return isItem ? (
93-
<PanelNavItem key={item.id} item={item} parentIsAccordion={parentIsAccordion} />
94-
) : (
95-
<PanelGroup navNode={item} key={item.id} />
96-
);
97-
});
98-
},
99-
[filteredChildren]
100-
);
90+
return filteredChildren.map((item, i) => {
91+
const isItem = item.renderAs === 'item' || !item.children;
92+
return isItem ? (
93+
<PanelNavItem key={item.id} item={item} />
94+
) : (
95+
<PanelGroup navNode={item} key={item.id} />
96+
);
97+
});
98+
}, [filteredChildren]);
10199

102100
if (!filteredChildren?.length || !someChildIsVisible(filteredChildren)) {
103101
return null;
@@ -109,7 +107,7 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
109107
{spaceBefore !== null && <EuiSpacer size={spaceBefore} />}
110108
<EuiAccordion
111109
id={id}
112-
buttonContent={title}
110+
buttonContent={withBadge ? <SubItemTitle item={navNode} /> : title}
113111
className={classNames.accordion}
114112
buttonClassName={accordionButtonClassName}
115113
data-test-subj={groupTestSubj}
@@ -120,7 +118,7 @@ export const PanelGroup: FC<Props> = ({ navNode, isFirstInList, hasHorizontalRul
120118
>
121119
<>
122120
{!firstChildIsGroup && <EuiSpacer size="s" />}
123-
{renderChildren({ parentIsAccordion: true })}
121+
{renderChildren()}
124122
</>
125123
</EuiAccordion>
126124
{appendHorizontalRule && <EuiHorizontalRule margin="xs" />}

src/platform/packages/shared/shared-ux/chrome/navigation/src/ui/components/panel/panel_nav_item.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,9 @@ import { usePanel } from './context';
1919

2020
interface Props {
2121
item: ChromeProjectNavigationNode;
22-
parentIsAccordion?: boolean;
2322
}
2423

25-
export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
24+
export const PanelNavItem: FC<Props> = ({ item }) => {
2625
const { navigateToUrl } = useServices();
2726
const { close: closePanel } = usePanel();
2827
const { id, icon, deepLink, openInNewTab, isExternalLink, renderItem } = item;
@@ -46,7 +45,7 @@ export const PanelNavItem: FC<Props> = ({ item, parentIsAccordion }) => {
4645
) : (
4746
<EuiListGroupItem
4847
key={id}
49-
label={parentIsAccordion ? <SubItemTitle item={item} /> : item.title}
48+
label={<SubItemTitle item={item} />}
5049
wrapText
5150
className={classNames(
5251
'sideNavPanelLink',

src/platform/packages/shared/shared-ux/chrome/navigation/src/ui/navigation.stories.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
113113
href: '/app/kibana',
114114
icon: 'iInCircle',
115115
isExternalLink: true,
116+
withBadge: true,
116117
},
117118
{
118119
id: 'item02',
@@ -229,6 +230,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
229230
title: 'Item 19',
230231
icon: 'iInCircle',
231232
renderAs: 'accordion',
233+
withBadge: true,
232234
children: [
233235
{
234236
id: 'sub1',
@@ -294,6 +296,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
294296
path: '',
295297
icon: 'iInCircle',
296298
renderAs: 'panelOpener',
299+
withBadge: true,
297300
children: [
298301
{
299302
id: 'sub1',
@@ -411,6 +414,7 @@ const generalLayoutNavTree: NavigationTreeDefinitionUI = {
411414
path: '',
412415
renderAs: 'accordion',
413416
icon: 'iInCircle',
417+
withBadge: true,
414418
children: [
415419
{
416420
id: 'item-beta',

0 commit comments

Comments
 (0)