Skip to content

Commit 7f2f760

Browse files
committed
more fixes from review comments
1 parent b5ec92f commit 7f2f760

2 files changed

Lines changed: 34 additions & 102 deletions

File tree

packages/react-icons/src/__tests__/createIcon.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react';
22
import {
33
IconDefinition,
44
CreateIconBaseProps,
5-
CreateIconLegacyProps,
5+
CreateIconProps,
66
createIcon,
77
createIconBase,
88
SVGPathObject
@@ -71,7 +71,7 @@ test('sets correct svgPath if string', () => {
7171
});
7272

7373
test('accepts legacy flat createIcon({ svgPath }) shape', () => {
74-
const legacyDef: CreateIconLegacyProps = {
74+
const legacyDef: CreateIconProps = {
7575
name: 'LegacyIcon',
7676
width: 10,
7777
height: 20,

packages/react-icons/src/createIcon.tsx

Lines changed: 32 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface IconDefinitionBase {
1616

1717
/**
1818
* On-disk / nested icon data: `svgPathData` (preferred) or deprecated `svgPath` (at least one is required at
19-
* runtime for rendering; if both are set, `svgPathData` takes precedence in {@link resolveSvgPathData}).
19+
* runtime for rendering; if both are set, `svgPathData` takes precedence when path data is resolved).
2020
*/
2121
export interface IconDefinition extends IconDefinitionBase {
2222
svgPathData?: string | SVGPathObject[];
@@ -26,12 +26,6 @@ export interface IconDefinition extends IconDefinitionBase {
2626
svgPath?: string | SVGPathObject[];
2727
}
2828

29-
/**
30-
* {@link createIconBase} and rendering use this after {@link resolveSvgPathData} — not part of the public
31-
* type surface.
32-
*/
33-
type NormalizedIconDefinition = Required<Pick<IconDefinition, 'svgPathData'>> & IconDefinition;
34-
3529
/**
3630
* Nested (current) public API: `{ icon, rhUiIcon?, name? }` as produced by the icon generator and
3731
* `createIconBase` consumers.
@@ -43,24 +37,17 @@ export interface CreateIconBaseProps {
4337
}
4438

4539
/**
46-
* **Flat (legacy) public API** for {@link createIcon} only — not an alias of {@link IconDefinition} so
47-
* the legacy shape is obvious at the call site. `createIcon` maps this to {@link CreateIconBaseProps}.
40+
* @deprecated Prefer {@link createIconBase} with a nested {@link IconDefinition} using `svgPathData` instead
41+
* of this flat `createIcon` shape and legacy `svgPath` field.
4842
*/
49-
export interface CreateIconLegacyProps {
43+
export interface CreateIconProps {
5044
name?: string;
5145
width: number;
5246
height: number;
5347
xOffset?: number;
5448
yOffset?: number;
55-
svgPathData?: string | SVGPathObject[];
56-
/**
57-
* @deprecated Use {@link CreateIconLegacyProps.svgPathData} instead.
58-
*/
5949
svgPath?: string | SVGPathObject[];
6050
svgClassName?: string;
61-
/**
62-
* Optional second variant for the `set="rh-ui"` / nested-inner-SVG layout, matching {@link createIconBase}.
63-
*/
6451
rhUiIcon?: IconDefinition | null;
6552
}
6653

@@ -75,44 +62,19 @@ export interface SVGIconProps extends Omit<React.HTMLProps<SVGElement>, 'ref'> {
7562

7663
let currentId = 0;
7764

78-
/** Returns path data from `svgPathData` or deprecated `svgPath` (prefers `svgPathData` when both exist). */
79-
function resolveSvgPathData(icon: IconDefinition): string | SVGPathObject[] {
80-
if ('svgPathData' in icon && icon.svgPathData !== undefined) {
81-
return icon.svgPathData;
82-
}
83-
if ('svgPath' in icon && icon.svgPath !== undefined) {
84-
return icon.svgPath;
85-
}
86-
throw new Error('@patternfly/react-icons: IconDefinition must define svgPathData or svgPath');
87-
}
88-
89-
/** Produces a single {@link NormalizedIconDefinition} for internal rendering. */
90-
function normalizeIconDefinition(icon: IconDefinition): NormalizedIconDefinition {
91-
return {
92-
name: icon.name,
93-
width: icon.width,
94-
height: icon.height,
95-
svgPathData: resolveSvgPathData(icon),
96-
xOffset: icon.xOffset,
97-
yOffset: icon.yOffset,
98-
svgClassName: icon.svgClassName
99-
};
100-
}
101-
102-
/** Renders <path> element(s) from resolved (normalized) path data. */
103-
function pathElementsFromResolvedData(svgPathData: string | SVGPathObject[]): ReactNode {
104-
return Array.isArray(svgPathData) ? (
65+
/** Renders the same path markup as the historical `createIcon` implementation. */
66+
function getSvgPaths(svgPathData: string | SVGPathObject[] | undefined): ReactNode {
67+
return svgPathData && Array.isArray(svgPathData) ? (
10568
svgPathData.map((pathObject, index) => (
10669
<path className={pathObject.className} key={`${pathObject.path}-${index}`} d={pathObject.path} />
10770
))
10871
) : (
109-
<path d={svgPathData} />
72+
<path d={svgPathData as string} />
11073
);
11174
}
11275

113-
/** Renders an inner `<svg>` with viewBox and path(s) for the dual-SVG (CSS swap) layout. */
114-
const createSvg = (icon: NormalizedIconDefinition, iconClassName: string) => {
115-
const { xOffset, yOffset, width, height, svgPathData, svgClassName } = icon;
76+
const createSvg = (icon: IconDefinition, iconClassName: string) => {
77+
const { xOffset, yOffset, width, height, svgPathData, svgClassName } = icon ?? {};
11678
const _xOffset = xOffset ?? 0;
11779
const _yOffset = yOffset ?? 0;
11880
const viewBox = [_xOffset, _yOffset, width, height].join(' ');
@@ -128,62 +90,29 @@ const createSvg = (icon: NormalizedIconDefinition, iconClassName: string) => {
12890

12991
return (
13092
<svg viewBox={viewBox} className={classNames.join(' ')}>
131-
{pathElementsFromResolvedData(svgPathData)}
93+
{getSvgPaths(svgPathData)}
13294
</svg>
13395
);
13496
};
13597

13698
/**
137-
* Preferred factory for **nested** icon config (`icon` and optional `rhUiIcon`). Package-generated icons use this.
138-
*
139-
* @param name Optional display name for the component; falls back to `icon.name` when not set.
140-
* @see {@link createIcon} for the legacy **flat** argument shape.
99+
* Factory for the nested / current icon API. Behavior matches the pre-split `createIcon` on `main` (this name
100+
* replaces the original export). For path mapping from the flat `svgPath` shape, use {@link createIcon} only.
141101
*/
142102
export function createIconBase({
143103
name,
144104
icon,
145105
rhUiIcon = null
146106
}: CreateIconBaseProps): React.ComponentClass<SVGIconProps> {
147-
if (icon == null) {
148-
const label = name != null ? ` (name: ${String(name)})` : '';
149-
throw new Error(`@patternfly/react-icons: createIconBase requires an \`icon\` definition${label}.`);
150-
}
151-
const normalizedIcon = normalizeIconDefinition(icon);
152-
const normalizedRhUiIcon = rhUiIcon != null ? normalizeIconDefinition(rhUiIcon) : null;
153-
const displayName = name ?? icon.name;
154-
155107
return class SVGIcon extends Component<SVGIconProps> {
156-
static displayName = displayName;
108+
static displayName = name;
157109

158110
id = `icon-title-${currentId++}`;
159111

160-
private warnedMissingRhUi = false;
161-
162112
static defaultProps: SVGIconProps = {
163113
noDefaultStyle: false
164114
};
165115

166-
private warnIfMissingRhUiMapping = () => {
167-
if (this.warnedMissingRhUi) {
168-
return;
169-
}
170-
if (this.props.set === 'rh-ui' && normalizedRhUiIcon === null) {
171-
this.warnedMissingRhUi = true;
172-
// eslint-disable-next-line no-console -- intentional dev-facing warning for invalid set/rh-ui pairing
173-
console.warn(
174-
`Set "rh-ui" was provided for ${displayName}, but no rh-ui icon data exists for this icon. The default icon will be rendered.`
175-
);
176-
}
177-
};
178-
179-
componentDidMount() {
180-
this.warnIfMissingRhUiMapping();
181-
}
182-
183-
componentDidUpdate() {
184-
this.warnIfMissingRhUiMapping();
185-
}
186-
187116
/** Renders one root `<svg>`; either a single variant or nested inner SVGs for RH UI swap. */
188117
render() {
189118
const { title, className: propsClassName, set, noDefaultStyle, ...props } = this.props;
@@ -195,10 +124,16 @@ export function createIconBase({
195124
classNames.push(propsClassName);
196125
}
197126

198-
if (set !== undefined || normalizedRhUiIcon === null) {
199-
const iconData: NormalizedIconDefinition =
200-
set === 'rh-ui' && normalizedRhUiIcon !== null ? normalizedRhUiIcon : normalizedIcon;
201-
const { xOffset, yOffset, width, height, svgPathData, svgClassName } = iconData;
127+
if (set === 'rh-ui' && rhUiIcon === null) {
128+
// eslint-disable-next-line no-console -- same behavior as main branch createIcon
129+
console.warn(
130+
`Set "rh-ui" was provided for ${name}, but no rh-ui icon data exists for this icon. The default icon will be rendered.`
131+
);
132+
}
133+
134+
if ((set === undefined && rhUiIcon === null) || set !== undefined) {
135+
const iconData = set !== undefined && set === 'rh-ui' && rhUiIcon !== null ? rhUiIcon : icon;
136+
const { xOffset, yOffset, width, height, svgPathData, svgClassName } = iconData ?? {};
202137
const _xOffset = xOffset ?? 0;
203138
const _yOffset = yOffset ?? 0;
204139
const viewBox = [_xOffset, _yOffset, width, height].join(' ');
@@ -207,8 +142,6 @@ export function createIconBase({
207142
classNames.push(svgClassName);
208143
}
209144

210-
const svgPaths = pathElementsFromResolvedData(svgPathData);
211-
212145
return (
213146
<svg
214147
className={classNames.join(' ')}
@@ -222,7 +155,7 @@ export function createIconBase({
222155
{...(props as Omit<React.SVGProps<SVGElement>, 'ref'>)} // Lie.
223156
>
224157
{hasTitle && <title id={this.id}>{title}</title>}
225-
{svgPaths}
158+
{getSvgPaths(svgPathData)}
226159
</svg>
227160
);
228161
}
@@ -238,20 +171,20 @@ export function createIconBase({
238171
{...(props as Omit<React.SVGProps<SVGElement>, 'ref'>)} // Lie.
239172
>
240173
{hasTitle && <title id={this.id}>{title}</title>}
241-
{createSvg(normalizedIcon, 'pf-v6-icon-default')}
242-
{normalizedRhUiIcon && createSvg(normalizedRhUiIcon, 'pf-v6-icon-rh-ui')}
174+
{icon && createSvg(icon, 'pf-v6-icon-default')}
175+
{rhUiIcon && createSvg(rhUiIcon, 'pf-v6-icon-rh-ui')}
243176
</svg>
244177
);
245178
}
246179
};
247180
}
248181

249182
/**
250-
* Flat **legacy** entry point: turn {@link CreateIconLegacyProps} into a nested
251-
* `icon: IconDefinition` with `svgPathData` resolved, then call {@link createIconBase} (all legacy mapping
252-
* lives in this function). Prefer {@link createIconBase} for the nested `icon` / `rhUiIcon` shape.
183+
* Flat **legacy** entry point: turn {@link CreateIconProps} (`svgPath` + layout fields) into a nested
184+
* `icon: IconDefinition` with `svgPathData` set from `svgPath`, then call {@link createIconBase}. Use
185+
* {@link createIconBase} directly for nested `icon` objects that already use `svgPathData`.
253186
*/
254-
export function createIcon(legacy: CreateIconLegacyProps): React.ComponentClass<SVGIconProps> {
187+
export function createIcon(legacy: CreateIconProps): React.ComponentClass<SVGIconProps> {
255188
const { rhUiIcon = null, ...flat } = legacy;
256189
const icon: IconDefinition = {
257190
name: flat.name,
@@ -260,8 +193,7 @@ export function createIcon(legacy: CreateIconLegacyProps): React.ComponentClass<
260193
xOffset: flat.xOffset,
261194
yOffset: flat.yOffset,
262195
svgClassName: flat.svgClassName,
263-
// Fold deprecated svgPath (and de-dupe vs svgPathData) in one place, then omit svgPath in the object.
264-
svgPathData: resolveSvgPathData(flat as IconDefinition)
196+
svgPathData: flat.svgPath
265197
};
266198
return createIconBase({ name: icon.name, icon, rhUiIcon });
267199
}

0 commit comments

Comments
 (0)