[KibanaPageLayout] Solution Nav specific styles & props (#100089)

* Fixing sticky nav
* Adding some side bar styles
* Added a built-in solution nav title with avatar icon
* Adding tutorial docs
* Added KibanaPageTemplateSolutionNavAvatar
* Added KibanaPageTemplateSolutionNav
* Increased limit to `core` / `kibanaReact` plugin because of additional CSS
This commit is contained in:
Caroline Horn 2021-05-25 13:28:05 -04:00 committed by GitHub
parent 111e15a054
commit bca1c14f9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 755 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View file

@ -9,13 +9,13 @@ tags: ['kibana', 'dev', 'ui', 'tutorials']
`KibanaPageTemplate` is a thin wrapper around [EuiPageTemplate](https://elastic.github.io/eui/#/layout/page) that makes setting up common types of Kibana pages quicker and easier while also adhering to any Kibana-specific requirements and patterns.
Refer to EUI's documentation on [EuiPageTemplate](https://elastic.github.io/eui/#/layout/page) for constructing page layouts.
Refer to EUI's documentation on [**EuiPageTemplate**](https://elastic.github.io/eui/#/layout/page) for constructing page layouts.
## `isEmptyState`
Use the `isEmptyState` prop for when there is no page content to show. For example, before the user has created something, when no search results are found, before data is populated, or when permissions aren't met.
The default empty state uses any `pageHeader` info provided to populate an [`EuiEmptyPrompt`](https://elastic.github.io/eui/#/display/empty-prompt) and uses the `centeredBody` template type.
The default empty state uses any `pageHeader` info provided to populate an [**EuiEmptyPrompt**](https://elastic.github.io/eui/#/display/empty-prompt) and uses the `centeredBody` template type.
```tsx
<KibanaPageTemplate
@ -84,3 +84,36 @@ When passing both a `pageHeader` configuration and `isEmptyState`, the component
```
![Screenshot of demo custom empty state code with a page header. Shows the Kibana navigation bars, a level 1 heading "Dashboards", and a centered empty state with the a level 2 heading "No data", body text "You have no data. Would you like some of ours?", and a button that says "Get sample data".](../assets/kibana_header_and_empty_state.png)
## `solutionNav`
To add left side navigation for your solution, we recommend passing [**EuiSideNav**](https://elastic.github.io/eui/#/navigation/side-nav) props to the `solutionNav` prop. The template component will then handle the mobile views and add the solution nav embellishments. On top of the EUI props, you'll need to pass your solution `name` and an optional `icon`.
If you need to custom side bar content, you will need to pass you own navigation component to `pageSideBar`. We still recommend using [**EuiSideNav**](https://elastic.github.io/eui/#/navigation/side-nav).
When using `EuiSideNav`, root level items should not be linked but provide section labelling only.
```tsx
<KibanaPageTemplate
solutionNav={{
name: 'Management',
icon: 'managementApp',
items: [
{
name: 'Root item',
items: [
{ name: 'Navigation item', href: '#' },
{ name: 'Navigation item', href: '#' },
]
}
]
}}
>
{...}
</KibanaPageTemplate>
```
![Screenshot of Stack Management empty state with a provided solution navigation shown on the left, outlined in pink.](../assets/kibana_template_solution_nav.png)
![Screenshots of Stack Management page in mobile view. Menu closed on the left, menu open on the right.](../assets/kibana_template_solution_nav_mobile.png)

View file

@ -9,7 +9,7 @@ pageLoadAssetSize:
charts: 195358
cloud: 21076
console: 46091
core: 414000
core: 432925
crossClusterReplication: 65408
dashboard: 374194
dashboardEnhanced: 65646

View file

@ -49,6 +49,13 @@
top: $headerHeight;
height: calc(100% - #{$headerHeight});
}
@include euiBreakpoint('m', 'l', 'xl') {
.euiPageSideBar--sticky {
max-height: calc(100vh - #{$headerHeight});
top: #{$headerHeight};
}
}
}
.kbnBody {

View file

@ -2,6 +2,7 @@
exports[`KibanaPageTemplate render basic template 1`] = `
<EuiPageTemplate
paddingSize="l"
pageHeader={
Object {
"description": "test",
@ -12,12 +13,23 @@ exports[`KibanaPageTemplate render basic template 1`] = `
"title": "test",
}
}
pageSideBarProps={
Object {
"className": "kbnPageTemplate__pageSideBar",
}
}
restrictWidth={true}
/>
`;
exports[`KibanaPageTemplate render custom empty prompt only 1`] = `
<EuiPageTemplate
paddingSize="none"
pageSideBarProps={
Object {
"className": "kbnPageTemplate__pageSideBar",
}
}
restrictWidth={true}
template="centeredBody"
>
@ -33,6 +45,7 @@ exports[`KibanaPageTemplate render custom empty prompt only 1`] = `
exports[`KibanaPageTemplate render custom empty prompt with page header 1`] = `
<EuiPageTemplate
paddingSize="l"
pageHeader={
Object {
"description": "test",
@ -43,6 +56,11 @@ exports[`KibanaPageTemplate render custom empty prompt with page header 1`] = `
"title": "test",
}
}
pageSideBarProps={
Object {
"className": "kbnPageTemplate__pageSideBar",
}
}
restrictWidth={true}
template="centeredContent"
>
@ -58,6 +76,12 @@ exports[`KibanaPageTemplate render custom empty prompt with page header 1`] = `
exports[`KibanaPageTemplate render default empty prompt 1`] = `
<EuiPageTemplate
paddingSize="none"
pageSideBarProps={
Object {
"className": "kbnPageTemplate__pageSideBar",
}
}
restrictWidth={true}
template="centeredBody"
>
@ -72,7 +96,76 @@ exports[`KibanaPageTemplate render default empty prompt 1`] = `
test
</p>
}
iconColor=""
iconType="test"
/>
</EuiPageTemplate>
`;
exports[`KibanaPageTemplate render solutionNav 1`] = `
<EuiPageTemplate
paddingSize="l"
pageHeader={
Object {
"description": "test",
"iconType": "test",
"rightSideItems": Array [
"test",
],
"title": "test",
}
}
pageSideBar={
<KibanaPageTemplateSolutionNav
icon="solution"
items={
Array [
Object {
"id": "1",
"items": Array [
Object {
"id": "1.1",
"name": "Ingest Node Pipelines",
},
Object {
"id": "1.2",
"name": "Logstash Pipelines",
},
Object {
"id": "1.3",
"name": "Beats Central Management",
},
],
"name": "Ingest",
},
Object {
"id": "2",
"items": Array [
Object {
"id": "2.1",
"name": "Index Management",
},
Object {
"id": "2.2",
"name": "Index Lifecycle Policies",
},
Object {
"id": "2.3",
"name": "Snapshot and Restore",
},
],
"name": "Data",
},
]
}
name="Solution"
/>
}
pageSideBarProps={
Object {
"className": "kbnPageTemplate__pageSideBar",
}
}
restrictWidth={true}
/>
`;

View file

@ -0,0 +1,15 @@
$euiSideNavEmphasizedBackgroundColor: transparentize($euiColorLightShade, .7);
.kbnPageTemplate__pageSideBar {
padding: $euiSizeL;
background:
linear-gradient(160deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSizeXL, rgba(#FFF, 0) 0),
linear-gradient(175deg, $euiSideNavEmphasizedBackgroundColor 0, $euiSideNavEmphasizedBackgroundColor $euiSize, rgba(#FFF, 0) 0);
}
@include euiBreakpoint('xs','s') {
.kbnPageTemplate__pageSideBar {
width: auto;
padding: 0;
}
}

View file

@ -10,6 +10,46 @@ import React from 'react';
import { shallow } from 'enzyme';
import { KibanaPageTemplate } from './page_template';
import { EuiEmptyPrompt } from '@elastic/eui';
import { KibanaPageTemplateSolutionNavProps } from './solution_nav';
const navItems: KibanaPageTemplateSolutionNavProps['items'] = [
{
name: 'Ingest',
id: '1',
items: [
{
name: 'Ingest Node Pipelines',
id: '1.1',
},
{
name: 'Logstash Pipelines',
id: '1.2',
},
{
name: 'Beats Central Management',
id: '1.3',
},
],
},
{
name: 'Data',
id: '2',
items: [
{
name: 'Index Management',
id: '2.1',
},
{
name: 'Index Lifecycle Policies',
id: '2.2',
},
{
name: 'Snapshot and Restore',
id: '2.3',
},
],
},
];
describe('KibanaPageTemplate', () => {
test('render default empty prompt', () => {
@ -66,4 +106,23 @@ describe('KibanaPageTemplate', () => {
);
expect(component).toMatchSnapshot();
});
test('render solutionNav', () => {
const component = shallow(
<KibanaPageTemplate
pageHeader={{
iconType: 'test',
title: 'test',
description: 'test',
rightSideItems: ['test'],
}}
solutionNav={{
name: 'Solution',
icon: 'solution',
items: navItems,
}}
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -5,10 +5,21 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import './page_template.scss';
import React, { FunctionComponent } from 'react';
import classNames from 'classnames';
import { EuiEmptyPrompt, EuiPageTemplate, EuiPageTemplateProps } from '@elastic/eui';
import React, { FunctionComponent } from 'react';
import {
KibanaPageTemplateSolutionNav,
KibanaPageTemplateSolutionNavProps,
} from './solution_nav/solution_nav';
/**
* A thin wrapper around EuiPageTemplate with a few Kibana specific additions
*/
export type KibanaPageTemplateProps = EuiPageTemplateProps & {
/**
* Changes the template type depending on other props provided.
@ -17,6 +28,10 @@ export type KibanaPageTemplateProps = EuiPageTemplateProps & {
* With `pageHeader` and `children`: Uses `centeredContent`
*/
isEmptyState?: boolean;
/**
* Quick creation of EuiSideNav. Hooks up mobile instance too
*/
solutionNav?: KibanaPageTemplateSolutionNavProps;
};
export const KibanaPageTemplate: FunctionComponent<KibanaPageTemplateProps> = ({
@ -27,6 +42,8 @@ export const KibanaPageTemplate: FunctionComponent<KibanaPageTemplateProps> = ({
restrictWidth = true,
bottomBar,
bottomBarProps,
pageSideBar,
solutionNav,
...rest
}) => {
// Needed for differentiating between union types
@ -38,6 +55,13 @@ export const KibanaPageTemplate: FunctionComponent<KibanaPageTemplateProps> = ({
};
}
/**
* Create the solution nav component
*/
if (solutionNav) {
pageSideBar = <KibanaPageTemplateSolutionNav {...solutionNav} />;
}
/**
* An easy way to create the right content for empty pages
*/
@ -48,6 +72,7 @@ export const KibanaPageTemplate: FunctionComponent<KibanaPageTemplateProps> = ({
children = (
<EuiEmptyPrompt
iconType={iconType}
iconColor={''} // This is likely a solution or app logo, so keep it multi-color
title={pageTitle ? <h1>{pageTitle}</h1> : undefined}
body={description ? <p>{description}</p> : undefined}
actions={rightSideItems}
@ -62,8 +87,14 @@ export const KibanaPageTemplate: FunctionComponent<KibanaPageTemplateProps> = ({
return (
<EuiPageTemplate
template={template}
pageHeader={pageHeader}
restrictWidth={restrictWidth}
paddingSize={template === 'centeredBody' ? 'none' : 'l'}
pageHeader={pageHeader}
pageSideBar={pageSideBar}
pageSideBarProps={{
...rest.pageSideBarProps,
className: classNames('kbnPageTemplate__pageSideBar', rest.pageSideBarProps?.className),
}}
{...localBottomBarProps}
{...rest}
>

View file

@ -0,0 +1,238 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`KibanaPageTemplateSolutionNav accepts EuiSideNavProps 1`] = `
<div
className="kbnPageTemplateSolutionNav"
>
<EuiTitle
className="kbnPageTemplateSolutionNav__title"
id="kbnPageTemplateSolutionNav__title_generated-id"
size="xs"
>
<h2>
<strong>
Solution
</strong>
</h2>
</EuiTitle>
<EuiSideNav
aria-labelledby="kbnPageTemplateSolutionNav__title_generated-id"
data-test-subj="DTS"
isOpenOnMobile={false}
items={
Array [
Object {
"id": "1",
"items": Array [
Object {
"id": "1.1",
"name": "Ingest Node Pipelines",
},
Object {
"id": "1.2",
"name": "Logstash Pipelines",
},
Object {
"id": "1.3",
"name": "Beats Central Management",
},
],
"name": "Ingest",
},
Object {
"id": "2",
"items": Array [
Object {
"id": "2.1",
"name": "Index Management",
},
Object {
"id": "2.2",
"name": "Index Lifecycle Policies",
},
Object {
"id": "2.3",
"name": "Snapshot and Restore",
},
],
"name": "Data",
},
]
}
mobileTitle={
<h2>
<FormattedMessage
defaultMessage="{solutionName} Menu"
id="kibana-react.pageTemplate.solutionNav.mobileTitleText"
values={
Object {
"solutionName": "Solution",
}
}
/>
</h2>
}
toggleOpenOnMobile={[Function]}
/>
</div>
`;
exports[`KibanaPageTemplateSolutionNav renders 1`] = `
<div
className="kbnPageTemplateSolutionNav"
>
<EuiTitle
className="kbnPageTemplateSolutionNav__title"
id="kbnPageTemplateSolutionNav__title_generated-id"
size="xs"
>
<h2>
<strong>
Solution
</strong>
</h2>
</EuiTitle>
<EuiSideNav
aria-labelledby="kbnPageTemplateSolutionNav__title_generated-id"
isOpenOnMobile={false}
items={
Array [
Object {
"id": "1",
"items": Array [
Object {
"id": "1.1",
"name": "Ingest Node Pipelines",
},
Object {
"id": "1.2",
"name": "Logstash Pipelines",
},
Object {
"id": "1.3",
"name": "Beats Central Management",
},
],
"name": "Ingest",
},
Object {
"id": "2",
"items": Array [
Object {
"id": "2.1",
"name": "Index Management",
},
Object {
"id": "2.2",
"name": "Index Lifecycle Policies",
},
Object {
"id": "2.3",
"name": "Snapshot and Restore",
},
],
"name": "Data",
},
]
}
mobileTitle={
<h2>
<FormattedMessage
defaultMessage="{solutionName} Menu"
id="kibana-react.pageTemplate.solutionNav.mobileTitleText"
values={
Object {
"solutionName": "Solution",
}
}
/>
</h2>
}
toggleOpenOnMobile={[Function]}
/>
</div>
`;
exports[`KibanaPageTemplateSolutionNav renders with icon 1`] = `
<div
className="kbnPageTemplateSolutionNav"
>
<EuiTitle
className="kbnPageTemplateSolutionNav__title"
id="kbnPageTemplateSolutionNav__title_generated-id"
size="xs"
>
<h2>
<KibanaPageTemplateSolutionNavAvatar
iconType="logoElastic"
name="Solution"
/>
<strong>
Solution
</strong>
</h2>
</EuiTitle>
<EuiSideNav
aria-labelledby="kbnPageTemplateSolutionNav__title_generated-id"
isOpenOnMobile={false}
items={
Array [
Object {
"id": "1",
"items": Array [
Object {
"id": "1.1",
"name": "Ingest Node Pipelines",
},
Object {
"id": "1.2",
"name": "Logstash Pipelines",
},
Object {
"id": "1.3",
"name": "Beats Central Management",
},
],
"name": "Ingest",
},
Object {
"id": "2",
"items": Array [
Object {
"id": "2.1",
"name": "Index Management",
},
Object {
"id": "2.2",
"name": "Index Lifecycle Policies",
},
Object {
"id": "2.3",
"name": "Snapshot and Restore",
},
],
"name": "Data",
},
]
}
mobileTitle={
<h2>
<KibanaPageTemplateSolutionNavAvatar
iconType="logoElastic"
name="Solution"
/>
<FormattedMessage
defaultMessage="{solutionName} Menu"
id="kibana-react.pageTemplate.solutionNav.mobileTitleText"
values={
Object {
"solutionName": "Solution",
}
}
/>
</h2>
}
toggleOpenOnMobile={[Function]}
/>
</div>
`;

View file

@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`KibanaPageTemplateSolutionNavAvatar renders 1`] = `
<EuiAvatar
className="kbnPageTemplateSolutionNavAvatar"
color="plain"
iconType="logoElastic"
name="Solution"
/>
`;

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { KibanaPageTemplateSolutionNav, KibanaPageTemplateSolutionNavProps } from './solution_nav';
export {
KibanaPageTemplateSolutionNavAvatar,
KibanaPageTemplateSolutionNavAvatarProps,
} from './solution_nav_avatar';

View file

@ -0,0 +1,22 @@
.kbnPageTemplateSolutionNav__title {
margin-bottom: $euiSizeL;
}
@include euiBreakpoint('xs','s') {
.kbnPageTemplateSolutionNav {
// TODO: Fix in EUI
.euiSideNav__mobileToggle {
height: auto;
font-size: $euiFontSizeM;
.euiButtonEmpty__text {
overflow: visible;
}
}
}
// Rely on the `mobileToggle` of the EuiSideNav component to title the navigation list
.kbnPageTemplateSolutionNav__title {
display: none;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { KibanaPageTemplateSolutionNav, KibanaPageTemplateSolutionNavProps } from './solution_nav';
const items: KibanaPageTemplateSolutionNavProps['items'] = [
{
name: 'Ingest',
id: '1',
items: [
{
name: 'Ingest Node Pipelines',
id: '1.1',
},
{
name: 'Logstash Pipelines',
id: '1.2',
},
{
name: 'Beats Central Management',
id: '1.3',
},
],
},
{
name: 'Data',
id: '2',
items: [
{
name: 'Index Management',
id: '2.1',
},
{
name: 'Index Lifecycle Policies',
id: '2.2',
},
{
name: 'Snapshot and Restore',
id: '2.3',
},
],
},
];
describe('KibanaPageTemplateSolutionNav', () => {
test('renders', () => {
const component = shallow(<KibanaPageTemplateSolutionNav name="Solution" items={items} />);
expect(component).toMatchSnapshot();
});
test('renders with icon', () => {
const component = shallow(
<KibanaPageTemplateSolutionNav name="Solution" icon="logoElastic" items={items} />
);
expect(component).toMatchSnapshot();
});
test('accepts EuiSideNavProps', () => {
const component = shallow(
<KibanaPageTemplateSolutionNav name="Solution" data-test-subj="DTS" items={items} />
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,103 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import './solution_nav.scss';
import React, { FunctionComponent, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiTitle, EuiSideNav, EuiSideNavProps, htmlIdGenerator } from '@elastic/eui';
import {
KibanaPageTemplateSolutionNavAvatar,
KibanaPageTemplateSolutionNavAvatarProps,
} from './solution_nav_avatar';
export type KibanaPageTemplateSolutionNavProps = EuiSideNavProps<{}> & {
/**
* Name of the solution, i.e. "Observability"
*/
name: KibanaPageTemplateSolutionNavAvatarProps['name'];
/**
* Solution logo, i.e. "logoObservability"
*/
icon?: KibanaPageTemplateSolutionNavAvatarProps['iconType'];
};
/**
* A wrapper around EuiSideNav but also creates the appropriate title with optional solution logo
*/
export const KibanaPageTemplateSolutionNav: FunctionComponent<KibanaPageTemplateSolutionNavProps> = ({
name,
icon,
items,
...rest
}) => {
const [isSideNavOpenOnMobile, setisSideNavOpenOnMobile] = useState(false);
const toggleOpenOnMobile = () => {
setisSideNavOpenOnMobile(!isSideNavOpenOnMobile);
};
/**
* Create the avatar.
*/
let solutionAvatar;
if (icon) {
solutionAvatar = <KibanaPageTemplateSolutionNavAvatar iconType={icon} name={name} />;
}
/**
* Create the required title.
* a11y: Since the heading can't be nested inside `<nav>`, we have to hook them up via `aria-labelledby`
*/
const titleID = htmlIdGenerator('kbnPageTemplateSolutionNav__title')();
const solutionNavTitle = (
<EuiTitle size="xs" id={titleID} className="kbnPageTemplateSolutionNav__title">
<h2>
{solutionAvatar}
<strong>{name}</strong>
</h2>
</EuiTitle>
);
/**
* Create the side nav component
*/
let sideNav;
if (items) {
const mobileTitleText = (
<FormattedMessage
id="kibana-react.pageTemplate.solutionNav.mobileTitleText"
defaultMessage="{solutionName} Menu"
values={{ solutionName: name || 'Navigation' }}
/>
);
sideNav = (
<EuiSideNav
aria-labelledby={titleID}
mobileTitle={
<h2>
{solutionAvatar}
{mobileTitleText}
</h2>
}
toggleOpenOnMobile={toggleOpenOnMobile}
isOpenOnMobile={isSideNavOpenOnMobile}
items={items}
{...rest}
/>
);
}
return (
<div className="kbnPageTemplateSolutionNav">
{solutionNavTitle}
{sideNav}
</div>
);
};

View file

@ -0,0 +1,4 @@
.kbnPageTemplateSolutionNavAvatar {
@include euiBottomShadowSmall;
margin-right: $euiSize;
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { KibanaPageTemplateSolutionNavAvatar } from './solution_nav_avatar';
describe('KibanaPageTemplateSolutionNavAvatar', () => {
test('renders', () => {
const component = shallow(
<KibanaPageTemplateSolutionNavAvatar name="Solution" iconType="logoElastic" />
);
expect(component).toMatchSnapshot();
});
});

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import './solution_nav_avatar.scss';
import React, { FunctionComponent } from 'react';
import classNames from 'classnames';
import { EuiAvatar, EuiAvatarProps } from '@elastic/eui';
export type KibanaPageTemplateSolutionNavAvatarProps = EuiAvatarProps;
/**
* Applies extra styling to a typical EuiAvatar
*/
export const KibanaPageTemplateSolutionNavAvatar: FunctionComponent<KibanaPageTemplateSolutionNavAvatarProps> = ({
className,
...rest
}) => {
return (
<EuiAvatar
className={classNames('kbnPageTemplateSolutionNavAvatar', className)}
color="plain"
{...rest}
/>
);
};