Skip to content

Commit fa16a3b

Browse files
feat(Icons): Allow more complex paths and svgs (#11864)
Allow for arrays of path data in addition to simple strings. Data can include classNames as well as multiple paths.
1 parent 696b937 commit fa16a3b

File tree

10 files changed

+85
-41
lines changed

10 files changed

+85
-41
lines changed

packages/react-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"tslib": "^2.8.1"
5555
},
5656
"devDependencies": {
57-
"@patternfly/patternfly": "6.3.0-prerelease.24",
57+
"@patternfly/patternfly": "6.3.0-prerelease.26",
5858
"case-anything": "^3.1.2",
5959
"css": "^3.0.0",
6060
"fs-extra": "^11.3.0"

packages/react-docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"test:a11y": "patternfly-a11y --config patternfly-a11y.config"
2424
},
2525
"dependencies": {
26-
"@patternfly/patternfly": "6.3.0-prerelease.24",
26+
"@patternfly/patternfly": "6.3.0-prerelease.26",
2727
"@patternfly/react-charts": "workspace:^",
2828
"@patternfly/react-code-editor": "workspace:^",
2929
"@patternfly/react-core": "workspace:^",

packages/react-icons/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
PatternFly Icons as React Components.
44

5-
## Usage
5+
## Usage
66

77
```jsx
88
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
@@ -18,38 +18,40 @@ All icons from @patternfly/react-icons have the HTML class `pf-v6-svg` applied a
1818

1919
If not using @patternfly/react-icons in conjunction with @patternfly/react-styles, then the following generic styles will need to be applied to the icons: `height="1em", style="vertical-align: -0.125em;" width="1em"`
2020

21-
If using @patternfly/react-icons in conjunction with @patternfly/react-core, icons can be further styled by wrapping an icon from
21+
If using @patternfly/react-icons in conjunction with @patternfly/react-core, icons can be further styled by wrapping an icon from
2222
@patternfly/react-icons in a PatternFly icon component.
2323

2424
## Adding icons
2525

26-
Icons for this package are generated from the `@fortawesome/free-solid-svg-icons` package. To add more to what is generated, modify the [icons.js](./build/icons.js) file in the build folder.
26+
Icons for this package are generated from the `@fortawesome/free-solid-svg-icons` package.
27+
28+
If you have some custom icon defined by an SVG path the best way to add such icon to this repository is to add its path definition in the [customIcons.mjs](./scripts/icons/customIcons.mjs) file.
2729

28-
If you have some custom icon defined by svg path the best way to add such icon to this repository is to add its path definition in [pfIcons.js](./build/pfIcons.js) file in the build folder.
2930
```JS
30-
module.exports = {
31-
pfIcons: {
31+
export default {
3232
// ... other icon defintions
3333
bigPlus: {width: 1024, height: 1024, svgPathData: 'M2 1 h1 v1 h1 v1 h-1 v1 h-1 v-1 h-1 v-1 h1 z'}
34-
}
3534
}
3635
```
3736

3837
## Tree shaking
3938

4039
Ensure optimization.sideEffects is set to true within your Webpack config:
40+
4141
```JS
4242
optimization: {
4343
sideEffects: true
4444
}
4545
```
4646

4747
Use ESM module imports to enable tree shaking with no additional setup required.
48+
4849
```JS
4950
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
5051
```
5152

5253
To enable tree shaking with named imports for CJS modules, utilize [babel-plugin-transform-imports](https://www.npmjs.com/package/babel-plugin-transform-imports) and update a babel.config.js file to utilize the plugin:
54+
5355
```JS
5456
module.exports = {
5557
presets: ["@babel/preset-env", "@babel/preset-react"],
@@ -66,4 +68,3 @@ module.exports = {
6668
]
6769
}
6870
```
69-

packages/react-icons/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@fortawesome/free-brands-svg-icons": "^5.15.4",
3434
"@fortawesome/free-regular-svg-icons": "^5.15.4",
3535
"@fortawesome/free-solid-svg-icons": "^5.15.4",
36-
"@patternfly/patternfly": "6.3.0-prerelease.24",
36+
"@patternfly/patternfly": "6.3.0-prerelease.26",
3737
"fs-extra": "^11.3.0",
3838
"tslib": "^2.8.1"
3939
},

packages/react-icons/scripts/writeIcons.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ exports.${jsName}Config = {
1919
name: '${jsName}',
2020
height: ${icon.height},
2121
width: ${icon.width},
22-
svgPath: '${icon.svgPathData}',
22+
svgPath: ${JSON.stringify(icon.svgPathData)},
2323
yOffset: ${icon.yOffset || 0},
2424
xOffset: ${icon.xOffset || 0},
25+
svgClassName: ${JSON.stringify(icon.svgClassName)},
2526
};
2627
exports.${jsName} = require('../createIcon').createIcon(exports.${jsName}Config);
2728
exports["default"] = exports.${jsName};
@@ -38,9 +39,10 @@ export const ${jsName}Config = {
3839
name: '${jsName}',
3940
height: ${icon.height},
4041
width: ${icon.width},
41-
svgPath: '${icon.svgPathData}',
42+
svgPath: ${JSON.stringify(icon.svgPathData)},
4243
yOffset: ${icon.yOffset || 0},
4344
xOffset: ${icon.xOffset || 0},
45+
svgClassName: ${JSON.stringify(icon.svgClassName)},
4446
};
4547
4648
export const ${jsName} = createIcon(${jsName}Config);
@@ -57,9 +59,10 @@ export declare const ${jsName}Config: {
5759
name: '${jsName}',
5860
height: ${icon.height},
5961
width: ${icon.width},
60-
svgPath: '${icon.svgPathData}',
62+
svgPath: ${JSON.stringify(icon.svgPathData)},
6163
yOffset: ${icon.yOffset || 0},
6264
xOffset: ${icon.xOffset || 0},
65+
svgClassName: ${JSON.stringify(icon.svgClassName)},
6366
};
6467
export declare const ${jsName}: ComponentClass<SVGIconProps>;
6568
export default ${jsName};

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ const iconDef = {
88
svgPath: 'svgPath'
99
};
1010

11+
const iconDefWithArrayPath = {
12+
name: 'IconName',
13+
width: 10,
14+
height: 20,
15+
svgPath: [
16+
{ path: 'svgPath1', className: 'class1' },
17+
{ path: 'svgPath2', className: 'class2' }
18+
],
19+
svgClassName: 'test'
20+
};
21+
1122
const SVGIcon = createIcon(iconDef);
23+
const SVGArrayIcon = createIcon(iconDefWithArrayPath);
1224

1325
test('sets correct viewBox', () => {
1426
render(<SVGIcon />);
@@ -18,11 +30,27 @@ test('sets correct viewBox', () => {
1830
);
1931
});
2032

21-
test('sets correct svgPath', () => {
33+
test('sets correct svgPath if string', () => {
2234
render(<SVGIcon />);
2335
expect(screen.getByRole('img', { hidden: true }).querySelector('path')).toHaveAttribute('d', iconDef.svgPath);
2436
});
2537

38+
test('sets correct svgPath if array', () => {
39+
render(<SVGArrayIcon />);
40+
const paths = screen.getByRole('img', { hidden: true }).querySelectorAll('path');
41+
expect(paths).toHaveLength(2);
42+
expect(paths[0]).toHaveAttribute('d', iconDefWithArrayPath.svgPath[0].path);
43+
expect(paths[1]).toHaveAttribute('d', iconDefWithArrayPath.svgPath[1].path);
44+
expect(paths[0]).toHaveClass(iconDefWithArrayPath.svgPath[0].className);
45+
expect(paths[1]).toHaveClass(iconDefWithArrayPath.svgPath[1].className);
46+
});
47+
48+
test('sets correct svgClassName', () => {
49+
render(<SVGArrayIcon />);
50+
const paths = screen.getByRole('img', { hidden: true });
51+
expect(paths).toHaveClass(iconDefWithArrayPath.svgClassName);
52+
});
53+
2654
test('aria-hidden is true if no title is specified', () => {
2755
render(<SVGIcon />);
2856
expect(screen.getByRole('img', { hidden: true })).toHaveAttribute('aria-hidden', 'true');

packages/react-icons/src/createIcon.tsx

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { Component } from 'react';
22

3+
export interface SVGPathObject {
4+
path: string;
5+
className?: string;
6+
}
37
export interface IconDefinition {
48
name?: string;
59
width: number;
610
height: number;
7-
svgPath: string;
11+
svgPath: string | SVGPathObject[];
812
xOffset?: number;
913
yOffset?: number;
14+
svgClassName?: string;
1015
}
1116

1217
export interface SVGIconProps extends Omit<React.HTMLProps<SVGElement>, 'ref'> {
@@ -25,23 +30,31 @@ export function createIcon({
2530
yOffset = 0,
2631
width,
2732
height,
28-
svgPath
33+
svgPath,
34+
svgClassName
2935
}: IconDefinition): React.ComponentClass<SVGIconProps> {
3036
return class SVGIcon extends Component<SVGIconProps> {
3137
static displayName = name;
3238

3339
id = `icon-title-${currentId++}`;
3440

3541
render() {
36-
const { title, className, ...props } = this.props;
37-
const classes = className ? `pf-v6-svg ${className}` : 'pf-v6-svg';
42+
const { title, className: propsClassName, ...props } = this.props;
3843

3944
const hasTitle = Boolean(title);
4045
const viewBox = [xOffset, yOffset, width, height].join(' ');
4146

47+
const classNames = ['pf-v6-svg'];
48+
if (svgClassName) {
49+
classNames.push(svgClassName);
50+
}
51+
if (propsClassName) {
52+
classNames.push(propsClassName);
53+
}
54+
4255
return (
4356
<svg
44-
className={classes}
57+
className={classNames.join(' ')}
4558
viewBox={viewBox}
4659
fill="currentColor"
4760
aria-labelledby={hasTitle ? this.id : null}
@@ -52,7 +65,13 @@ export function createIcon({
5265
{...(props as Omit<React.SVGProps<SVGElement>, 'ref'>)} // Lie.
5366
>
5467
{hasTitle && <title id={this.id}>{title}</title>}
55-
<path d={svgPath} />
68+
{Array.isArray(svgPath) ? (
69+
svgPath.map((pathObject, index) => (
70+
<path className={pathObject.className} key={`${pathObject.path}-${index}`} d={pathObject.path} />
71+
))
72+
) : (
73+
<path d={svgPath} />
74+
)}
5675
</svg>
5776
);
5877
}

packages/react-styles/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"clean": "rimraf dist css"
2020
},
2121
"devDependencies": {
22-
"@patternfly/patternfly": "6.3.0-prerelease.24",
22+
"@patternfly/patternfly": "6.3.0-prerelease.26",
2323
"change-case": "^5.4.4",
2424
"fs-extra": "^11.3.0"
2525
},

packages/react-tokens/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
"clean": "rimraf dist"
3030
},
3131
"devDependencies": {
32-
"@adobe/css-tools": "^4.4.2",
33-
"@patternfly/patternfly": "6.3.0-prerelease.24",
32+
"@patternfly/patternfly": "6.3.0-prerelease.26",
33+
"css": "^3.0.0",
3434
"fs-extra": "^11.3.0"
3535
}
3636
}

yarn.lock

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,6 @@ __metadata:
1212
languageName: node
1313
linkType: hard
1414

15-
"@adobe/css-tools@npm:^4.4.2":
16-
version: 4.4.2
17-
resolution: "@adobe/css-tools@npm:4.4.2"
18-
checksum: 10c0/19433666ad18536b0ed05d4b53fbb3dd6ede266996796462023ec77a90b484890ad28a3e528cdf3ab8a65cb2fcdff5d8feb04db6bc6eed6ca307c40974239c94
19-
languageName: node
20-
linkType: hard
21-
2215
"@ampproject/remapping@npm:^2.2.0":
2316
version: 2.3.0
2417
resolution: "@ampproject/remapping@npm:2.3.0"
@@ -3639,10 +3632,10 @@ __metadata:
36393632
languageName: node
36403633
linkType: hard
36413634

3642-
"@patternfly/patternfly@npm:6.3.0-prerelease.24":
3643-
version: 6.3.0-prerelease.24
3644-
resolution: "@patternfly/patternfly@npm:6.3.0-prerelease.24"
3645-
checksum: 10c0/127bc928ebb67c35fb2ab42c91a63a950dfb5f68972acdf8b94aabc48ca13fee164b02f10a632712165d977eb04bc28de562e107db9f9053b68c47c61bdc83cf
3635+
"@patternfly/patternfly@npm:6.3.0-prerelease.26":
3636+
version: 6.3.0-prerelease.26
3637+
resolution: "@patternfly/patternfly@npm:6.3.0-prerelease.26"
3638+
checksum: 10c0/3c5d72a9ed2ded48469f5d385eaf861dbcc0f36e42e0760b05757a40145b2970e71a354da382672cacb962fa1707b604c4342ebfeb9d74059e87049a94a4bd83
36463639
languageName: node
36473640
linkType: hard
36483641

@@ -3740,7 +3733,7 @@ __metadata:
37403733
version: 0.0.0-use.local
37413734
resolution: "@patternfly/react-core@workspace:packages/react-core"
37423735
dependencies:
3743-
"@patternfly/patternfly": "npm:6.3.0-prerelease.24"
3736+
"@patternfly/patternfly": "npm:6.3.0-prerelease.26"
37443737
"@patternfly/react-icons": "workspace:^"
37453738
"@patternfly/react-styles": "workspace:^"
37463739
"@patternfly/react-tokens": "workspace:^"
@@ -3761,7 +3754,7 @@ __metadata:
37613754
resolution: "@patternfly/react-docs@workspace:packages/react-docs"
37623755
dependencies:
37633756
"@patternfly/documentation-framework": "npm:^6.5.20"
3764-
"@patternfly/patternfly": "npm:6.3.0-prerelease.24"
3757+
"@patternfly/patternfly": "npm:6.3.0-prerelease.26"
37653758
"@patternfly/patternfly-a11y": "npm:5.1.0"
37663759
"@patternfly/react-charts": "workspace:^"
37673760
"@patternfly/react-code-editor": "workspace:^"
@@ -3801,7 +3794,7 @@ __metadata:
38013794
"@fortawesome/free-brands-svg-icons": "npm:^5.15.4"
38023795
"@fortawesome/free-regular-svg-icons": "npm:^5.15.4"
38033796
"@fortawesome/free-solid-svg-icons": "npm:^5.15.4"
3804-
"@patternfly/patternfly": "npm:6.3.0-prerelease.24"
3797+
"@patternfly/patternfly": "npm:6.3.0-prerelease.26"
38053798
fs-extra: "npm:^11.3.0"
38063799
tslib: "npm:^2.8.1"
38073800
peerDependencies:
@@ -3885,7 +3878,7 @@ __metadata:
38853878
version: 0.0.0-use.local
38863879
resolution: "@patternfly/react-styles@workspace:packages/react-styles"
38873880
dependencies:
3888-
"@patternfly/patternfly": "npm:6.3.0-prerelease.24"
3881+
"@patternfly/patternfly": "npm:6.3.0-prerelease.26"
38893882
change-case: "npm:^5.4.4"
38903883
fs-extra: "npm:^11.3.0"
38913884
languageName: unknown
@@ -3926,8 +3919,8 @@ __metadata:
39263919
version: 0.0.0-use.local
39273920
resolution: "@patternfly/react-tokens@workspace:packages/react-tokens"
39283921
dependencies:
3929-
"@adobe/css-tools": "npm:^4.4.2"
3930-
"@patternfly/patternfly": "npm:6.3.0-prerelease.24"
3922+
"@patternfly/patternfly": "npm:6.3.0-prerelease.26"
3923+
css: "npm:^3.0.0"
39313924
fs-extra: "npm:^11.3.0"
39323925
languageName: unknown
39333926
linkType: soft

0 commit comments

Comments
 (0)