1- import { Component } from 'react' ;
1+ import { Component , type ReactNode } from 'react' ;
22
33export interface SVGPathObject {
44 path : string ;
@@ -51,18 +51,28 @@ export interface CreateIconBaseProps {
5151export type CreateIconProps = CreateIconBaseProps ;
5252
5353/**
54- * Props for {@link createIcon} — flat {@link IconDefinition} fields at the top level, optionally with
55- * `rhUiIcon`, matching the pre–nested-config API.
54+ * The **flat (legacy) public API** for {@link createIcon}: the same path/view fields as
55+ * {@link IconDefinition} at the top level, plus optional `rhUiIcon` for the dual-`set` layout.
56+ * `createIcon` is the only entry that accepts this shape; it maps to {@link CreateIconBaseProps} and calls
57+ * {@link createIconBase} (see {@link createIcon}).
5658 */
57- export type CreateIconLegacyProps = IconDefinition & {
59+ export interface CreateIconLegacyProps {
60+ name ?: string ;
61+ width : number ;
62+ height : number ;
63+ xOffset ?: number ;
64+ yOffset ?: number ;
65+ svgPathData ?: string | SVGPathObject [ ] ;
66+ /**
67+ * @deprecated Use {@link CreateIconLegacyProps.svgPathData} instead.
68+ */
69+ svgPath ?: string | SVGPathObject [ ] ;
70+ svgClassName ?: string ;
71+ /**
72+ * Optional second variant for the `set="rh-ui"` / nested-inner-SVG layout, matching {@link createIconBase}.
73+ */
5874 rhUiIcon ?: IconDefinition | null ;
59- } ;
60-
61- /**
62- * @deprecated The previous `createIcon` accepted only a flat {@link IconDefinition}. Use {@link createIcon}
63- * for that shape, or {@link createIconBase} with nested `icon` / `rhUiIcon`.
64- */
65- export type LegacyFlatIconDefinition = IconDefinition ;
75+ }
6676
6777export interface SVGIconProps extends Omit < React . HTMLProps < SVGElement > , 'ref' > {
6878 title ?: string ;
@@ -99,14 +109,25 @@ function normalizeIconDefinition(icon: IconDefinition): IconDefinitionWithSvgPat
99109 } ;
100110}
101111
112+ /** Renders <path> element(s) from resolved (normalized) path data. */
113+ function pathElementsFromResolvedData ( svgPathData : string | SVGPathObject [ ] ) : ReactNode {
114+ return Array . isArray ( svgPathData ) ? (
115+ svgPathData . map ( ( pathObject , index ) => (
116+ < path className = { pathObject . className } key = { `${ pathObject . path } -${ index } ` } d = { pathObject . path } />
117+ ) )
118+ ) : (
119+ < path d = { svgPathData } />
120+ ) ;
121+ }
122+
102123/** Renders an inner `<svg>` with viewBox and path(s) for the dual-SVG (CSS swap) layout. */
103124const createSvg = ( icon : IconDefinitionWithSvgPathData , iconClassName : string ) => {
104- const { xOffset, yOffset, width, height, svgPathData, svgClassName } = icon ?? { } ;
125+ const { xOffset, yOffset, width, height, svgPathData, svgClassName } = icon ;
105126 const _xOffset = xOffset ?? 0 ;
106127 const _yOffset = yOffset ?? 0 ;
107128 const viewBox = [ _xOffset , _yOffset , width , height ] . join ( ' ' ) ;
108129
109- const classNames = [ ] ;
130+ const classNames : string [ ] = [ ] ;
110131
111132 if ( svgClassName ) {
112133 classNames . push ( svgClassName ) ;
@@ -115,18 +136,9 @@ const createSvg = (icon: IconDefinitionWithSvgPathData, iconClassName: string) =
115136 classNames . push ( iconClassName ) ;
116137 }
117138
118- const svgPaths =
119- svgPathData && Array . isArray ( svgPathData ) ? (
120- svgPathData . map ( ( pathObject , index ) => (
121- < path className = { pathObject . className } key = { `${ pathObject . path } -${ index } ` } d = { pathObject . path } />
122- ) )
123- ) : (
124- < path d = { svgPathData as string } />
125- ) ;
126-
127139 return (
128140 < svg viewBox = { viewBox } className = { classNames . join ( ' ' ) } >
129- { svgPaths }
141+ { pathElementsFromResolvedData ( svgPathData ) }
130142 </ svg >
131143 ) ;
132144} ;
@@ -155,10 +167,33 @@ export function createIconBase({
155167
156168 id = `icon-title-${ currentId ++ } ` ;
157169
170+ private warnedMissingRhUi = false ;
171+
158172 static defaultProps : SVGIconProps = {
159173 noDefaultStyle : false
160174 } ;
161175
176+ private warnIfMissingRhUiMapping = ( ) => {
177+ if ( this . warnedMissingRhUi ) {
178+ return ;
179+ }
180+ if ( this . props . set === 'rh-ui' && normalizedRhUiIcon === null ) {
181+ this . warnedMissingRhUi = true ;
182+ // eslint-disable-next-line no-console -- intentional dev-facing warning for invalid set/rh-ui pairing
183+ console . warn (
184+ `Set "rh-ui" was provided for ${ displayName } , but no rh-ui icon data exists for this icon. The default icon will be rendered.`
185+ ) ;
186+ }
187+ } ;
188+
189+ componentDidMount ( ) {
190+ this . warnIfMissingRhUiMapping ( ) ;
191+ }
192+
193+ componentDidUpdate ( ) {
194+ this . warnIfMissingRhUiMapping ( ) ;
195+ }
196+
162197 /** Renders one root `<svg>`; either a single variant or nested inner SVGs for RH UI swap. */
163198 render ( ) {
164199 const { title, className : propsClassName , set, noDefaultStyle, ...props } = this . props ;
@@ -170,18 +205,10 @@ export function createIconBase({
170205 classNames . push ( propsClassName ) ;
171206 }
172207
173- if ( set === 'rh-ui' && normalizedRhUiIcon === null ) {
174- // eslint-disable-next-line no-console
175- console . warn (
176- `Set "rh-ui" was provided for ${ displayName } , but no rh-ui icon data exists for this icon. The default icon will be rendered.`
177- ) ;
178- }
179-
180- if ( ( set === undefined && normalizedRhUiIcon === null ) || set !== undefined ) {
181- const iconData : IconDefinitionWithSvgPathData | undefined =
182- set !== undefined && set === 'rh-ui' && normalizedRhUiIcon !== null ? normalizedRhUiIcon : normalizedIcon ;
183- const { xOffset, yOffset, width, height, svgPathData, svgClassName } =
184- iconData ?? ( { } as Partial < IconDefinitionWithSvgPathData > ) ;
208+ if ( set !== undefined || normalizedRhUiIcon === null ) {
209+ const iconData : IconDefinitionWithSvgPathData =
210+ set === 'rh-ui' && normalizedRhUiIcon !== null ? normalizedRhUiIcon : normalizedIcon ;
211+ const { xOffset, yOffset, width, height, svgPathData, svgClassName } = iconData ;
185212 const _xOffset = xOffset ?? 0 ;
186213 const _yOffset = yOffset ?? 0 ;
187214 const viewBox = [ _xOffset , _yOffset , width , height ] . join ( ' ' ) ;
@@ -190,14 +217,7 @@ export function createIconBase({
190217 classNames . push ( svgClassName ) ;
191218 }
192219
193- const svgPaths =
194- svgPathData && Array . isArray ( svgPathData ) ? (
195- svgPathData . map ( ( pathObject , index ) => (
196- < path className = { pathObject . className } key = { `${ pathObject . path } -${ index } ` } d = { pathObject . path } />
197- ) )
198- ) : (
199- < path d = { svgPathData as string } />
200- ) ;
220+ const svgPaths = pathElementsFromResolvedData ( svgPathData ) ;
201221
202222 return (
203223 < svg
@@ -215,37 +235,34 @@ export function createIconBase({
215235 { svgPaths }
216236 </ svg >
217237 ) ;
218- } else {
219- return (
220- < svg
221- className = { classNames . join ( ' ' ) }
222- fill = "currentColor"
223- aria-labelledby = { hasTitle ? this . id : null }
224- aria-hidden = { hasTitle ? null : true }
225- role = "img"
226- width = "1em"
227- height = "1em"
228- { ...( props as Omit < React . SVGProps < SVGElement > , 'ref' > ) } // Lie.
229- >
230- { hasTitle && < title id = { this . id } > { title } </ title > }
231- { normalizedIcon && createSvg ( normalizedIcon , 'pf-v6-icon-default' ) }
232- { normalizedRhUiIcon && createSvg ( normalizedRhUiIcon , 'pf-v6-icon-rh-ui' ) }
233- </ svg >
234- ) ;
235238 }
239+ return (
240+ < svg
241+ className = { classNames . join ( ' ' ) }
242+ fill = "currentColor"
243+ aria-labelledby = { hasTitle ? this . id : null }
244+ aria-hidden = { hasTitle ? null : true }
245+ role = "img"
246+ width = "1em"
247+ height = "1em"
248+ { ...( props as Omit < React . SVGProps < SVGElement > , 'ref' > ) } // Lie.
249+ >
250+ { hasTitle && < title id = { this . id } > { title } </ title > }
251+ { createSvg ( normalizedIcon , 'pf-v6-icon-default' ) }
252+ { normalizedRhUiIcon && createSvg ( normalizedRhUiIcon , 'pf-v6-icon-rh-ui' ) }
253+ </ svg >
254+ ) ;
236255 }
237256 } ;
238257}
239258
240259/**
241- * Legacy-friendly factory: **flat** {@link IconDefinition} fields (plus optional `rhUiIcon`) and delegates to
242- * {@link createIconBase}. For nested configs, use {@link createIconBase} directly.
260+ * Legacy **flat** factory: maps {@link CreateIconLegacyProps} to {@link CreateIconBaseProps} and calls
261+ * {@link createIconBase} (all remapping lives here; `createIconBase` only implements rendering). For the
262+ * nested `icon` / `rhUiIcon` shape, use {@link createIconBase} directly.
243263 */
244- export function createIcon ( props : CreateIconLegacyProps ) : React . ComponentClass < SVGIconProps > {
245- const { rhUiIcon, ...icon } = props ;
246- return createIconBase ( {
247- name : icon . name ,
248- icon,
249- rhUiIcon : rhUiIcon ?? null
250- } ) ;
264+ export function createIcon ( legacy : CreateIconLegacyProps ) : React . ComponentClass < SVGIconProps > {
265+ const { rhUiIcon = null , ...iconFields } = legacy ;
266+ const icon : IconDefinition = iconFields ;
267+ return createIconBase ( { name : icon . name , icon, rhUiIcon } ) ;
251268}
0 commit comments