Preserve CSS sizing for translated system icons.
Keep the original icon element as the rendered root so legacy MUI-backed icons can still be styled through CSS like other Backstage UI icons. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
+22
-6
@@ -73,12 +73,8 @@ describe('DefaultIconsApi', () => {
|
||||
// @ts-expect-error testing runtime behavior
|
||||
const result = icon({});
|
||||
expect(result.type).toBe('span');
|
||||
expect(result.props.style).toEqual({
|
||||
display: 'inline-flex',
|
||||
fontSize: '1.5rem',
|
||||
lineHeight: 0,
|
||||
});
|
||||
expect(result.props.children).toBe(element);
|
||||
expect(result.props.style).toEqual({ fontSize: '1.5rem' });
|
||||
expect(result.props.children).toBe(element.props.children);
|
||||
expect(api.getIcon('myIcon')).toBe(icon);
|
||||
});
|
||||
|
||||
@@ -92,6 +88,26 @@ describe('DefaultIconsApi', () => {
|
||||
expect(result.props.style.fontSize).toBe('1.25rem');
|
||||
});
|
||||
|
||||
it('should forward runtime props to the original icon element', () => {
|
||||
const element = createElement('svg', {
|
||||
className: 'existing',
|
||||
style: { color: 'red' },
|
||||
});
|
||||
const api = new DefaultIconsApi({ myIcon: element });
|
||||
const icon = api.getIcon('myIcon');
|
||||
|
||||
// @ts-expect-error testing runtime behavior
|
||||
const result = icon({ className: 'extra', style: { width: '2em' } });
|
||||
|
||||
expect(result.type).toBe('svg');
|
||||
expect(result.props.className).toBe('existing extra');
|
||||
expect(result.props.style).toEqual({
|
||||
color: 'red',
|
||||
fontSize: '1.5rem',
|
||||
width: '2em',
|
||||
});
|
||||
});
|
||||
|
||||
it('should wrap null IconElement in a component for getIcon()', () => {
|
||||
const api = new DefaultIconsApi({ empty: null });
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
IconElement,
|
||||
IconsApi,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { createElement, isValidElement } from 'react';
|
||||
import { cloneElement, createElement, isValidElement } from 'react';
|
||||
|
||||
const legacyFontSizeMap = {
|
||||
inherit: 'inherit',
|
||||
@@ -28,6 +28,14 @@ const legacyFontSizeMap = {
|
||||
large: '2.1875rem',
|
||||
} as const;
|
||||
|
||||
function mergeClassNames(...classNames: Array<string | undefined>) {
|
||||
const merged = classNames.filter(Boolean).join(' ');
|
||||
if (merged) {
|
||||
return merged;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation for the {@link IconsApi}
|
||||
*
|
||||
@@ -75,23 +83,36 @@ export class DefaultIconsApi implements IconsApi {
|
||||
if (el === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
component = ({ fontSize = 'medium' }) => {
|
||||
component = props => {
|
||||
if (el === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createElement(
|
||||
// eslint-disable-next-line react/forbid-elements
|
||||
'span',
|
||||
{
|
||||
style: {
|
||||
display: 'inline-flex',
|
||||
fontSize: legacyFontSizeMap[fontSize],
|
||||
lineHeight: 0,
|
||||
},
|
||||
const {
|
||||
fontSize = 'medium',
|
||||
className,
|
||||
style,
|
||||
...rest
|
||||
} = props as {
|
||||
fontSize?: keyof typeof legacyFontSizeMap;
|
||||
className?: string;
|
||||
style?: Record<string, unknown>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
const elementProps = el.props as {
|
||||
className?: string;
|
||||
style?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
return cloneElement(el, {
|
||||
...rest,
|
||||
className: mergeClassNames(elementProps.className, className),
|
||||
style: {
|
||||
...elementProps.style,
|
||||
fontSize: legacyFontSizeMap[fontSize],
|
||||
...style,
|
||||
},
|
||||
el,
|
||||
);
|
||||
});
|
||||
};
|
||||
this.#components.set(key, component);
|
||||
return component;
|
||||
|
||||
Reference in New Issue
Block a user