@@ -5,22 +5,49 @@ export interface SVGPathObject {
55 className ?: string ;
66}
77
8- export interface IconDefinition {
8+ export interface IconDefinitionBase {
99 name ?: string ;
1010 width : number ;
1111 height : number ;
12- svgPathData : string | SVGPathObject [ ] ;
1312 xOffset ?: number ;
1413 yOffset ?: number ;
1514 svgClassName ?: string ;
1615}
1716
17+ /**
18+ * SVG path content for one icon variant (default or rh-ui). At runtime at least one of
19+ * `svgPathData` or `svgPath` must be set; if both are present, `svgPathData` is used.
20+ */
21+ export interface IconDefinition extends IconDefinitionBase {
22+ svgPathData ?: string | SVGPathObject [ ] ;
23+ /**
24+ * @deprecated Use {@link IconDefinition.svgPathData} instead.
25+ */
26+ svgPath ?: string | SVGPathObject [ ] ;
27+ }
28+
29+ /** Narrows {@link IconDefinition} to the preferred shape with required `svgPathData`. */
30+ export type IconDefinitionWithSvgPathData = Required < Pick < IconDefinition , 'svgPathData' > > & IconDefinition ;
31+
32+ /**
33+ * @deprecated Use {@link IconDefinition} with `svgPathData` instead.
34+ * Narrows {@link IconDefinition} to the legacy shape with required `svgPath`.
35+ */
36+ export type IconDefinitionWithSvgPath = Required < Pick < IconDefinition , 'svgPath' > > & IconDefinition ;
37+
38+ /** When passing `icon` or `rhUiIcon` keys (nested form), `icon` is required at runtime. */
1839export interface CreateIconProps {
1940 name ?: string ;
2041 icon ?: IconDefinition ;
2142 rhUiIcon ?: IconDefinition | null ;
2243}
2344
45+ /**
46+ * @deprecated The previous `createIcon` accepted a flat {@link IconDefinition} with top-level
47+ * `svgPath`. Pass {@link CreateIconProps} with a nested `icon` field instead.
48+ */
49+ export type LegacyFlatIconDefinition = IconDefinition ;
50+
2451export interface SVGIconProps extends Omit < React . HTMLProps < SVGElement > , 'ref' > {
2552 title ?: string ;
2653 className ?: string ;
@@ -32,7 +59,70 @@ export interface SVGIconProps extends Omit<React.HTMLProps<SVGElement>, 'ref'> {
3259
3360let currentId = 0 ;
3461
35- const createSvg = ( icon : IconDefinition , iconClassName : string ) => {
62+ /** Returns path data from `svgPathData` or deprecated `svgPath` (prefers `svgPathData` when both exist). */
63+ function resolveSvgPathData ( icon : IconDefinition ) : string | SVGPathObject [ ] {
64+ if ( 'svgPathData' in icon && icon . svgPathData !== undefined ) {
65+ return icon . svgPathData ;
66+ }
67+ if ( 'svgPath' in icon && icon . svgPath !== undefined ) {
68+ return icon . svgPath ;
69+ }
70+ throw new Error ( '@patternfly/react-icons: IconDefinition must define svgPathData or svgPath' ) ;
71+ }
72+
73+ /** Produces a single {@link IconDefinitionWithSvgPathData} for internal rendering. */
74+ function normalizeIconDefinition ( icon : IconDefinition ) : IconDefinitionWithSvgPathData {
75+ return {
76+ name : icon . name ,
77+ width : icon . width ,
78+ height : icon . height ,
79+ svgPathData : resolveSvgPathData ( icon ) ,
80+ xOffset : icon . xOffset ,
81+ yOffset : icon . yOffset ,
82+ svgClassName : icon . svgClassName
83+ } ;
84+ }
85+
86+ /** True when the argument uses the nested `CreateIconProps` shape (`icon` and/or `rhUiIcon` keys). */
87+ function isNestedCreateIconProps ( arg : object ) : arg is CreateIconProps {
88+ return 'icon' in arg || 'rhUiIcon' in arg ;
89+ }
90+
91+ /** Props after resolving legacy `svgPath` and flat `createIcon` arguments. */
92+ interface NormalizedCreateIconProps {
93+ name ?: string ;
94+ icon ?: IconDefinitionWithSvgPathData ;
95+ rhUiIcon : IconDefinitionWithSvgPathData | null ;
96+ }
97+
98+ /**
99+ * Coerces legacy flat or nested props into normalized {@link NormalizedCreateIconProps}.
100+ * Nested input must include a non-null `icon` or throws.
101+ */
102+ function normalizeCreateIconArg ( arg : CreateIconProps | LegacyFlatIconDefinition ) : NormalizedCreateIconProps {
103+ if ( isNestedCreateIconProps ( arg ) ) {
104+ const p = arg as CreateIconProps ;
105+ if ( p . icon == null ) {
106+ const label = p . name != null ? ` (name: ${ String ( p . name ) } )` : '' ;
107+ throw new Error (
108+ `@patternfly/react-icons: createIcon requires an \`icon\` definition when using nested CreateIconProps${ label } .`
109+ ) ;
110+ }
111+ return {
112+ name : p . name ,
113+ icon : normalizeIconDefinition ( p . icon ) ,
114+ rhUiIcon : p . rhUiIcon != null ? normalizeIconDefinition ( p . rhUiIcon ) : null
115+ } ;
116+ }
117+ return {
118+ name : ( arg as LegacyFlatIconDefinition ) . name ,
119+ icon : normalizeIconDefinition ( arg as IconDefinition ) ,
120+ rhUiIcon : null
121+ } ;
122+ }
123+
124+ /** Renders an inner `<svg>` with viewBox and path(s) for the dual-SVG (CSS swap) layout. */
125+ const createSvg = ( icon : IconDefinitionWithSvgPathData , iconClassName : string ) => {
36126 const { xOffset, yOffset, width, height, svgPathData, svgClassName } = icon ?? { } ;
37127 const _xOffset = xOffset ?? 0 ;
38128 const _yOffset = yOffset ?? 0 ;
@@ -64,9 +154,33 @@ const createSvg = (icon: IconDefinition, iconClassName: string) => {
64154} ;
65155
66156/**
67- * Factory to create Icon class components for consumers
157+ * Builds a React **class** component that renders a PatternFly SVG icon (`role="img"`, optional `<title>` for a11y).
158+ *
159+ * **Argument shape — pick one:**
160+ *
161+ * 1. **`CreateIconProps` (preferred)** — `{ name?, icon?, rhUiIcon? }`. Dimensions and path data sit on `icon`
162+ * (and optionally on `rhUiIcon` for Red Hat UI–mapped icons). If the object **has an `icon` or `rhUiIcon` key**
163+ * (including `rhUiIcon: null`), this shape is assumed.
164+ *
165+ * 2. **Legacy flat `IconDefinition`** — the same fields as `icon`, but at the **top level** (no nested `icon`).
166+ * Still accepted so existing callers are not broken. Prefer migrating to `CreateIconProps`.
167+ *
168+ * **Path data on each `IconDefinition`:** use `svgPathData` (string or {@link SVGPathObject}[]). The old name
169+ * `svgPath` is deprecated but still read; `svgPathData` wins if both are present.
170+ *
171+ * **Default vs RH UI rendering:** If `rhUiIcon` is set and the consumer does **not** pass `set` on the component,
172+ * the output is an outer `<svg.pf-v6-svg>` containing **two** inner `<svg>`s (default + rh-ui) so CSS can swap
173+ * which variant is visible. If `set` is `"default"` or `"rh-ui"`, a **single** flat `<svg>` is rendered for that
174+ * variant. Requesting `set="rh-ui"` when there is no `rhUiIcon` falls back to the default glyph and logs a
175+ * `console.warn` (see implementation).
176+ *
177+ * @param arg Icon configuration: either {@link CreateIconProps} (nested `icon` / `rhUiIcon`) or a legacy flat
178+ * {@link LegacyFlatIconDefinition}. Runtime detection follows the rules in **Argument shape** above.
179+ * @returns A `ComponentClass<SVGIconProps>` — render it as `<YourIcon />` or with `title`, `className`, `set`, etc.
68180 */
69- export function createIcon ( { name, icon, rhUiIcon = null } : CreateIconProps ) : React . ComponentClass < SVGIconProps > {
181+ export function createIcon ( arg : CreateIconProps | LegacyFlatIconDefinition ) : React . ComponentClass < SVGIconProps > {
182+ const { name, icon, rhUiIcon = null } = normalizeCreateIconArg ( arg ) ;
183+
70184 return class SVGIcon extends Component < SVGIconProps > {
71185 static displayName = name ;
72186
@@ -76,10 +190,7 @@ export function createIcon({ name, icon, rhUiIcon = null }: CreateIconProps): Re
76190 noDefaultStyle : false
77191 } ;
78192
79- constructor ( props : SVGIconProps ) {
80- super ( props ) ;
81- }
82-
193+ /** Renders one root `<svg>`; either a single variant or nested inner SVGs for RH UI swap. */
83194 render ( ) {
84195 const { title, className : propsClassName , set, noDefaultStyle, ...props } = this . props ;
85196
@@ -98,8 +209,10 @@ export function createIcon({ name, icon, rhUiIcon = null }: CreateIconProps): Re
98209 }
99210
100211 if ( ( set === undefined && rhUiIcon === null ) || set !== undefined ) {
101- const iconData = set !== undefined && set === 'rh-ui' && rhUiIcon !== null ? rhUiIcon : icon ;
102- const { xOffset, yOffset, width, height, svgPathData, svgClassName } = iconData ?? { } ;
212+ const iconData : IconDefinitionWithSvgPathData | undefined =
213+ set !== undefined && set === 'rh-ui' && rhUiIcon !== null ? rhUiIcon : icon ;
214+ const { xOffset, yOffset, width, height, svgPathData, svgClassName } =
215+ iconData ?? ( { } as Partial < IconDefinitionWithSvgPathData > ) ;
103216 const _xOffset = xOffset ?? 0 ;
104217 const _yOffset = yOffset ?? 0 ;
105218 const viewBox = [ _xOffset , _yOffset , width , height ] . join ( ' ' ) ;
0 commit comments