further NFS icon migration and alignment
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -116,6 +116,17 @@ const makeSidebarStyles = (sidebarConfig: SidebarConfig) =>
|
||||
font: 'inherit',
|
||||
textTransform: 'none',
|
||||
},
|
||||
itemIcon: {
|
||||
display: 'inline-flex',
|
||||
fontSize: theme.typography.fontSize,
|
||||
lineHeight: 0,
|
||||
'& svg': {
|
||||
width: '1.5em',
|
||||
height: '1.5em',
|
||||
fontSize: 'inherit',
|
||||
flexShrink: 0,
|
||||
},
|
||||
},
|
||||
closed: {
|
||||
width: sidebarConfig.drawerWidthClosed,
|
||||
justifyContent: 'center',
|
||||
@@ -401,7 +412,9 @@ const SidebarItemBase = forwardRef<
|
||||
|
||||
const displayItemIcon = (
|
||||
<Box style={divStyle}>
|
||||
<Icon fontSize="small" />
|
||||
<Box component="span" className={classes.itemIcon}>
|
||||
<Icon fontSize="inherit" />
|
||||
</Box>
|
||||
{!isOpen && hasSubmenu ? <ArrowRightIcon fontSize="small" /> : <></>}
|
||||
</Box>
|
||||
);
|
||||
|
||||
+26
-1
@@ -59,6 +59,10 @@ describe('DefaultIconsApi', () => {
|
||||
expect(result).toBeTruthy();
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.type).toBe(MyIcon);
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.props.fontSize).toBe('inherit');
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.props.size).toBe('1em');
|
||||
});
|
||||
|
||||
it('should wrap IconElement values in a component for getIcon()', () => {
|
||||
@@ -69,10 +73,31 @@ describe('DefaultIconsApi', () => {
|
||||
expect(icon).toBeDefined();
|
||||
expect(typeof icon).toBe('function');
|
||||
// @ts-expect-error testing runtime behavior
|
||||
expect(icon({})).toBe(element);
|
||||
const result = icon({});
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.type).toBe('span');
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.props.style).toEqual({
|
||||
display: 'inline-flex',
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: 0,
|
||||
});
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.props.children).toBe(element);
|
||||
expect(api.getIcon('myIcon')).toBe(icon);
|
||||
});
|
||||
|
||||
it('should honor fontSize for getIcon()', () => {
|
||||
const element = createElement('svg');
|
||||
const api = new DefaultIconsApi({ myIcon: element });
|
||||
const icon = api.getIcon('myIcon');
|
||||
|
||||
// @ts-expect-error testing runtime behavior
|
||||
const result = icon({ fontSize: 'small' });
|
||||
// @ts-expect-error accessing internal React element structure
|
||||
expect(result.props.style.fontSize).toBe('1.25rem');
|
||||
});
|
||||
|
||||
it('should wrap null IconElement in a component for getIcon()', () => {
|
||||
const api = new DefaultIconsApi({ empty: null });
|
||||
|
||||
|
||||
@@ -21,6 +21,13 @@ import {
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { createElement, isValidElement } from 'react';
|
||||
|
||||
const legacyFontSizeMap = {
|
||||
inherit: 'inherit',
|
||||
small: '1.25rem',
|
||||
medium: '1.5rem',
|
||||
large: '2.1875rem',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Implementation for the {@link IconsApi}
|
||||
*
|
||||
@@ -65,7 +72,24 @@ export class DefaultIconsApi implements IconsApi {
|
||||
if (el === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
component = () => el;
|
||||
component = ({ fontSize = 'medium' }) => {
|
||||
if (el === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createElement(
|
||||
// eslint-disable-next-line react/forbid-elements
|
||||
'span',
|
||||
{
|
||||
style: {
|
||||
display: 'inline-flex',
|
||||
fontSize: legacyFontSizeMap[fontSize],
|
||||
lineHeight: 0,
|
||||
},
|
||||
},
|
||||
el,
|
||||
);
|
||||
};
|
||||
this.#components.set(key, component);
|
||||
return component;
|
||||
}
|
||||
|
||||
@@ -38,12 +38,18 @@ export type IconComponent = ComponentType<{
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The type used for icon elements throughout Backstage.
|
||||
* The type used for icon elements throughout Backstage. It is recommended to
|
||||
* use icons from `@remixicon/react`.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Icons should be exactly 24x24 pixels in size.
|
||||
*
|
||||
* Using icons from `@remixicon/react` is preferred, but using icons from
|
||||
* `@material-ui/icons` or `AppIcon` and its variants from
|
||||
* `@backstage/core-components` is supported but depreceated. When using these
|
||||
* icons, you must set the `fontSize` to `'inherit'`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type IconElement = JSX.Element | null;
|
||||
|
||||
@@ -43,7 +43,7 @@ const apiDocsNavItem = NavItemBlueprint.make({
|
||||
params: {
|
||||
title: 'APIs',
|
||||
routeRef: rootRoute,
|
||||
icon: () => <AppIcon id="kind:api" />,
|
||||
icon: () => <AppIcon fontSize="inherit" id="kind:api" />,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -211,7 +211,7 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'api-docs',
|
||||
title: 'APIs',
|
||||
icon: <AppIcon id="kind:api" />,
|
||||
icon: <AppIcon fontSize="inherit" id="kind:api" />,
|
||||
info: { packageJson: () => import('../package.json') },
|
||||
routes: {
|
||||
root: rootRoute,
|
||||
|
||||
@@ -102,7 +102,7 @@ export const visualizerPlugin = createFrontendPlugin({
|
||||
appVisualizerTreePage,
|
||||
appVisualizerDetailedPage,
|
||||
appVisualizerTextPage,
|
||||
appVisualizerNavItem,
|
||||
// appVisualizerNavItem,
|
||||
copyTreeAsJson,
|
||||
],
|
||||
});
|
||||
|
||||
@@ -172,6 +172,7 @@ function NavContentRenderer(props: {
|
||||
// We want the priority: page (config/params) -> nav item -> plugin -> pluginId
|
||||
const resolvedTitle = node.instance.getData(coreExtensionData.title);
|
||||
const pluginTitle = node.spec.plugin.title;
|
||||
const pluginIcon = node.spec.plugin.icon;
|
||||
const pluginId = node.spec.plugin.pluginId;
|
||||
const hasExplicitPageTitle =
|
||||
resolvedTitle !== undefined &&
|
||||
@@ -194,6 +195,8 @@ function NavContentRenderer(props: {
|
||||
icon = <NavItemIcon />;
|
||||
} else if (resolvedIcon) {
|
||||
icon = resolvedIcon;
|
||||
} else if (pluginIcon) {
|
||||
icon = pluginIcon;
|
||||
}
|
||||
|
||||
if (!title || !icon) {
|
||||
|
||||
@@ -45,7 +45,15 @@ export const IconsApi = ApiBlueprint.makeWithOverrides({
|
||||
return new DefaultIconsApi(
|
||||
inputs.icons
|
||||
.map(i => i.get(IconBundleBlueprint.dataRefs.icons))
|
||||
.reduce((acc, bundle) => ({ ...acc, ...bundle }), defaultIcons),
|
||||
.reduce(
|
||||
(acc, bundle) => ({ ...acc, ...bundle }),
|
||||
Object.fromEntries(
|
||||
Object.entries(defaultIcons).map(([key, Icon]) => [
|
||||
key,
|
||||
<Icon />,
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
@@ -69,7 +69,7 @@ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'catalog-unprocessed-entities',
|
||||
title: 'Unprocessed Entities',
|
||||
icon: <QueueIcon />,
|
||||
icon: <QueueIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../../package.json') },
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
|
||||
@@ -59,7 +59,7 @@ export const catalogPage = PageBlueprint.makeWithOverrides({
|
||||
return originalFactory({
|
||||
path: '/catalog',
|
||||
routeRef: rootRouteRef,
|
||||
icon: <CategoryIcon />,
|
||||
icon: <CategoryIcon fontSize="inherit" />,
|
||||
title: 'Catalog',
|
||||
loader: async () => {
|
||||
const { BaseCatalogPage } = await import('../components/CatalogPage');
|
||||
|
||||
@@ -40,7 +40,7 @@ import contextMenuItems from './contextMenuItems';
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'catalog',
|
||||
title: 'Catalog',
|
||||
icon: <CategoryIcon />,
|
||||
icon: <CategoryIcon fontSize="inherit" />,
|
||||
info: {
|
||||
packageJson: () => import('../../package.json'),
|
||||
},
|
||||
|
||||
@@ -89,7 +89,7 @@ export const devToolsNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'devtools',
|
||||
title: 'DevTools',
|
||||
icon: <BuildIcon />,
|
||||
icon: <BuildIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../../package.json') },
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
|
||||
@@ -61,7 +61,7 @@ const scaffolderEntityIconLink = EntityIconLinkBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'scaffolder',
|
||||
title: 'Create',
|
||||
icon: <CreateComponentIcon />,
|
||||
icon: <CreateComponentIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../../package.json') },
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
|
||||
@@ -277,7 +277,7 @@ export const searchNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'search',
|
||||
title: 'Search',
|
||||
icon: <SearchIcon />,
|
||||
icon: <SearchIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../package.json') },
|
||||
extensions: [searchApi, searchPage, searchNavItem],
|
||||
routes: {
|
||||
|
||||
@@ -280,7 +280,7 @@ const techDocsNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'techdocs',
|
||||
title: 'Docs',
|
||||
icon: <LibraryBooks />,
|
||||
icon: <LibraryBooks fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../../package.json') },
|
||||
extensions: [
|
||||
techDocsClientApi,
|
||||
|
||||
@@ -63,7 +63,7 @@ export const settingsNavItem = NavItemBlueprint.make({
|
||||
export default createFrontendPlugin({
|
||||
pluginId: 'user-settings',
|
||||
title: 'Settings',
|
||||
icon: <SettingsIcon />,
|
||||
icon: <SettingsIcon fontSize="inherit" />,
|
||||
info: { packageJson: () => import('../package.json') },
|
||||
extensions: [userSettingsPage, settingsNavItem],
|
||||
routes: {
|
||||
|
||||
Reference in New Issue
Block a user