Skip to content

Commit adb1186

Browse files
authored
Add support for duotone icons (#59)
1 parent bf4027e commit adb1186

File tree

6 files changed

+119
-5
lines changed

6 files changed

+119
-5
lines changed

Diff for: README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
+ [Change Color with a StyleSheet](#color-stylesheet-property)
2626
* [Size](#size)
2727
- [Features](#features)
28+
* [Duotone](#duotone)
2829
* [Masking](#masking)
2930
* [Power Transforms](#power-transforms)
3031
- [Frequent questions](#frequent-questions)
@@ -98,7 +99,13 @@ $ npm i --save @fortawesome/pro-solid-svg-icons
9899
$ npm i --save @fortawesome/pro-regular-svg-icons
99100
$ npm i --save @fortawesome/pro-light-svg-icons
100101
```
101-
**Duotone icons are currently in pre-release and are coming soon to this component.**
102+
103+
If you'd like to use Duotone icons, you'll need to add Duotone package:
104+
105+
```
106+
$ npm i --save @fortawesome/pro-duotone-svg-icons
107+
```
108+
102109

103110
## or with Yarn
104111

@@ -375,6 +382,14 @@ Note: the `height` and `width` props have been deprecated.
375382

376383
## Features
377384

385+
### Duotone
386+
387+
```javascript
388+
<FontAwesomeIcon icon="coffee" color="blue" secondaryColor="red" secondaryOpacity={ 0.4 } />
389+
```
390+
391+
You can specify the color and opacity for Duotone's secondary layer using the `secondaryColor` and `secondaryOpacity` props. Note that these are optional, and will simply default to using your primary color at 40% opacity.
392+
378393
### Masking
379394

380395
```javascript

Diff for: index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface Props {
1717
width?: number;
1818
size?: number;
1919
color?: string;
20+
secondaryColor?: string;
21+
secondaryOpacity?: number;
2022
mask?: IconProp;
2123
transform?: string | Transform;
2224
style?: FontAwesomeIconStyle;

Diff for: package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/components/FontAwesomeIcon.js

+22-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const { width: windowWidth, height: windowHeight } = Dimensions.get('window')
99

1010
export const DEFAULT_SIZE = 16
1111
export const DEFAULT_COLOR = '#000'
12+
export const DEFAULT_SECONDARY_OPACITY = 0.4
1213

1314
// Deprecated height and width defaults
1415
const DEFAULT_HEIGHT = windowHeight * 0.1
@@ -66,6 +67,13 @@ export default function FontAwesomeIcon(props) {
6667
// This is the color that will be passed to the "fill" prop of the Svg element
6768
const color = props.color || style.color || DEFAULT_COLOR
6869

70+
// This is the color that will be passed to the "fill" prop of the secondary Path element child (in Duotone Icons)
71+
// `null` value will result in using the primary color, at 40% opacity
72+
const secondaryColor = props.secondaryColor || null
73+
74+
// Secondary layer opacity should default to 0.4, unless a specific opacity value or a specific secondary color was given
75+
const secondaryOpacity = props.secondaryOpacity || (secondaryColor ? 1 : DEFAULT_SECONDARY_OPACITY)
76+
6977
// To avoid confusion down the line, we'll remove properties from the StyleSheet, like color, that are being overridden
7078
// or resolved in other ways, to avoid ambiguity as to which inputs cause which outputs in the underlying rendering process.
7179
// In other words, we don't want color (for example) to be specified via two different inputs.
@@ -89,7 +97,14 @@ export default function FontAwesomeIcon(props) {
8997
resolvedHeight = resolvedWidth = size || DEFAULT_SIZE
9098
}
9199

92-
const extraProps = { height: resolvedHeight, width: resolvedWidth, fill: color, style: modifiedStyle }
100+
const extraProps = {
101+
height: resolvedHeight,
102+
width: resolvedWidth,
103+
fill: color,
104+
secondaryFill: secondaryColor,
105+
secondaryOpacity: secondaryOpacity,
106+
style: modifiedStyle
107+
}
93108

94109
Object.keys(props).forEach(key => {
95110
if (!FontAwesomeIcon.defaultProps.hasOwnProperty(key)) {
@@ -112,6 +127,10 @@ FontAwesomeIcon.propTypes = {
112127

113128
color: PropTypes.string,
114129

130+
secondaryColor: PropTypes.string,
131+
132+
secondaryOpacity: PropTypes.number,
133+
115134
style: PropTypes.oneOfType([
116135
PropTypes.shape({ ...ViewPropTypes.style }),
117136
PropTypes.array
@@ -138,6 +157,8 @@ FontAwesomeIcon.defaultProps = {
138157
transform: null,
139158
style: {},
140159
color: null,
160+
secondaryColor: null,
161+
secondaryOpacity: null,
141162
height: undefined,
142163
width: undefined,
143164
// Once the deprecation of height and width props is complete, let's put the real default prop value for size here.

Diff for: src/components/__tests__/FontAwesomeIcon.test.js

+70
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ const faCircle = {
3131
]
3232
}
3333

34+
const faAcorn = {
35+
prefix: 'fad',
36+
iconName: 'acorn',
37+
icon: [
38+
640,
39+
512,
40+
[],
41+
'f0f4',
42+
['M32 256h384a258.87 258.87 0 0 1-143.11 231.55L224 512l-48.89-24.45A258.87 258.87 0 0 1 32 256z', 'M448 160v32a32 32 0 0 1-32 32H32a32 32 0 0 1-32-32v-32a96 96 0 0 1 96-96h106a132.41 132.41 0 0 1 29.41-58.64 15.7 15.7 0 0 1 11.31-5.3 15.44 15.44 0 0 1 12 4.72L266 16.1a16 16 0 0 1 .66 21.9 84.32 84.32 0 0 0-15.16 26H352a96 96 0 0 1 96 96z']
43+
]
44+
}
45+
46+
3447
fontawesome.library.add(faCoffee, faCircle)
3548

3649
test.skip('renders with icon specified as array', () => {
@@ -212,3 +225,60 @@ describe('when style is given an array and not an object', () => {
212225
expect(tree.props.style.filter(s => s.backgroundColor === 'yellow').length).toEqual(1)
213226
})
214227
});
228+
229+
describe('duotone support', () => {
230+
describe('when NO secondary color or opacity are given', () => {
231+
test('use the primary color at 40% opacity as the secondary color', () => {
232+
const styles = StyleSheet.create({
233+
icon: {
234+
color: 'blue'
235+
}
236+
})
237+
const tree = renderer.create(<FontAwesomeIcon icon={ faAcorn } style={ styles.icon }/>).toJSON()
238+
const primaryLayer = tree.children[0].children[0].children[1]
239+
const secondaryLayer = tree.children[0].children[0].children[0]
240+
expect(primaryLayer.props.fill[1].toString(16)).toEqual('ff0000ff')
241+
expect(secondaryLayer.props.fillOpacity).toEqual(0.4)
242+
})
243+
})
244+
describe('when secondary opacity was given, but NO secondary color is given', () => {
245+
test('use the primary color with the secondary opacity given', () => {
246+
const styles = StyleSheet.create({
247+
icon: {
248+
color: 'blue'
249+
}
250+
})
251+
const tree = renderer.create(<FontAwesomeIcon icon={ faAcorn } style={ styles.icon } secondaryOpacity={ 0.123 } />).toJSON()
252+
const primaryLayer = tree.children[0].children[0].children[1]
253+
const secondaryLayer = tree.children[0].children[0].children[0]
254+
expect(primaryLayer.props.fill[1].toString(16)).toEqual('ff0000ff')
255+
expect(secondaryLayer.props.fillOpacity).toEqual(0.123)
256+
})
257+
})
258+
describe('when secondary color is given, but no secondary opacity', () => {
259+
test('use the given secondary color, with opacity set to 1', () => {
260+
const styles = StyleSheet.create({
261+
icon: {
262+
color: 'blue'
263+
}
264+
})
265+
const tree = renderer.create(<FontAwesomeIcon icon={ faAcorn } style={ styles.icon } secondaryColor={ "red" } />).toJSON()
266+
const secondaryLayer = tree.children[0].children[0].children[0]
267+
expect(secondaryLayer.props.fill[1].toString(16)).toEqual('ffff0000')
268+
expect(secondaryLayer.props.fillOpacity).toEqual(1)
269+
})
270+
})
271+
describe('when secondary color and secondary opacity are given', () => {
272+
test('use both the given secondary color and opacity', () => {
273+
const styles = StyleSheet.create({
274+
icon: {
275+
color: 'blue'
276+
}
277+
})
278+
const tree = renderer.create(<FontAwesomeIcon icon={ faAcorn } style={ styles.icon } secondaryColor={ "red" } secondaryOpacity={ 0.123 } />).toJSON()
279+
const secondaryLayer = tree.children[0].children[0].children[0]
280+
expect(secondaryLayer.props.fill[1].toString(16)).toEqual('ffff0000')
281+
expect(secondaryLayer.props.fillOpacity).toEqual(0.123)
282+
})
283+
})
284+
})

Diff for: src/converter.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ function convert(createElement, element, extraProps = {}) {
1616
return element
1717
}
1818

19+
const isDuotone = (element.children || []).length === 2
1920
const children = (element.children || []).map(
20-
child => {
21-
return convert(createElement, child)
21+
(child, childIndex) => {
22+
const isDuotoneSecondLayer = isDuotone && childIndex === 0;
23+
const fill = isDuotoneSecondLayer
24+
? extraProps.secondaryFill
25+
: extraProps.fill;
26+
const fillOpacity = isDuotoneSecondLayer ? extraProps.secondaryOpacity : 1;
27+
return convert(createElement, child, { ...extraProps, fill, fillOpacity });
2228
}
2329
)
2430

0 commit comments

Comments
 (0)