Created
May 9, 2024 07:45
-
-
Save eduardinni/3208a4c8159f689950ec9995a04097c8 to your computer and use it in GitHub Desktop.
react-native-fontawesome FontAwesomeIcon typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { ReactNode } from 'react'; | |
import humps from 'humps'; | |
import { | |
Svg, | |
Path, | |
Rect, | |
Defs, | |
Mask, | |
G, | |
ClipPath, | |
type NativeProps, | |
} from 'react-native-svg'; | |
import { NativeComponentType } from 'react-native/Libraries/Utilities/codegenNativeComponent'; | |
import type { AbstractElement } from '@fortawesome/fontawesome-svg-core'; | |
function svgObjectMap(tag: string): NativeComponentType<NativeProps> { | |
let element; | |
switch (tag) { | |
case 'svg': | |
element = Svg as unknown; | |
break; | |
case 'path': | |
element = Path as unknown; | |
break; | |
case 'rect': | |
element = Rect as unknown; | |
break; | |
case 'defs': | |
element = Defs as unknown; | |
break; | |
case 'mask': | |
element = Mask as unknown; | |
break; | |
case 'g': | |
element = G as unknown; | |
break; | |
case 'clipPath': | |
element = ClipPath as unknown; | |
break; | |
default: | |
element = null; | |
} | |
return element as NativeComponentType<NativeProps>; | |
} | |
export default function convert( | |
createElement: typeof React.createElement, | |
element: AbstractElement, | |
): ReactNode { | |
if (typeof element === 'string') { | |
return element; | |
} | |
const children: ReactNode[] | undefined = (element.children || []).map( | |
(child: AbstractElement) => { | |
return convert(createElement, child) as ReactNode; | |
}, | |
); | |
const mixins = Object.keys(element.attributes || {}).reduce( | |
(acc: { attrs: any }, key: string) => { | |
const val = element.attributes[key]; | |
switch (key) { | |
case 'class': | |
case 'role': | |
case 'xmlns': | |
delete element.attributes[key]; | |
break; | |
case 'focusable': | |
acc.attrs[key] = val === 'true'; | |
break; | |
default: | |
if ( | |
key.indexOf('aria-') === 0 || | |
key.indexOf('data-') === 0 || | |
(key === 'fill' && val === 'currentColor') | |
) { | |
delete element.attributes[key]; | |
} else { | |
acc.attrs[humps.camelize(key)] = val; | |
} | |
} | |
return acc; | |
}, | |
{ attrs: {} }, | |
); | |
return createElement( | |
svgObjectMap(element.tag), | |
{ ...mixins.attrs }, | |
...children, | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { StyleProp, StyleSheet } from 'react-native'; | |
import { icon } from '@fortawesome/fontawesome-svg-core'; | |
import convert from '../util/converter'; | |
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'; | |
import type { | |
IconParams, | |
Transform, | |
IconLookup, | |
Styles, | |
AbstractElement, | |
} from '@fortawesome/fontawesome-svg-core'; | |
export const DEFAULT_SIZE = 16; | |
export const DEFAULT_COLOR = '#000'; | |
export const DEFAULT_SECONDARY_OPACITY = 0.4; | |
type FontAwesomeIconProps = { | |
icon: IconDefinition; | |
mask?: IconLookup; | |
maskId?: string; | |
transform?: Transform; | |
style?: StyleProp<object>; | |
color?: string | null; | |
secondaryColor?: string | null; | |
secondaryOpacity?: number | null; | |
size?: number; | |
}; | |
export default function FontAwesomeIcon({ | |
icon: iconArgs, | |
mask, | |
maskId, | |
transform, | |
style = {}, | |
color = null, | |
secondaryColor = null, | |
secondaryOpacity = null, | |
size = DEFAULT_SIZE, | |
}: FontAwesomeIconProps) { | |
const styleFlatten: Styles = StyleSheet.flatten(style) as Styles; | |
const renderIconParams: IconParams = { | |
transform, | |
mask, | |
maskId, | |
}; | |
const renderedIcon = icon(iconArgs as IconLookup, renderIconParams); | |
if (!renderedIcon) { | |
return null; | |
} | |
const { abstract } = renderedIcon; | |
// This is the color that will be passed to the "fill" prop of the Svg element | |
const resolvedColor = color || styleFlatten.color || DEFAULT_COLOR; | |
// This is the color that will be passed to the "fill" prop of the secondary Path element child (in Duotone Icons) | |
// `null` value will result in using the primary color, at 40% opacity | |
const resolvedSecondaryColor = secondaryColor || resolvedColor; | |
// Secondary layer opacity should default to 0.4, unless a specific opacity value or a specific secondary color was given | |
const resolvedSecondaryOpacity = | |
secondaryOpacity || DEFAULT_SECONDARY_OPACITY; | |
// To avoid confusion down the line, we'll remove properties from the StyleSheet, like color, that are being overridden | |
// or resolved in other ways, to avoid ambiguity as to which inputs cause which outputs in the underlying rendering process. | |
// In other words, we don't want color (for example) to be specified via two different inputs. | |
const { color: styleColor, ...modifiedStyle } = styleFlatten; | |
const resolvedHeight = size || DEFAULT_SIZE; | |
const resolvedWidth = size || DEFAULT_SIZE; | |
const rootAttributes = abstract[0].attributes; | |
rootAttributes.height = resolvedHeight; | |
rootAttributes.width = resolvedWidth; | |
rootAttributes.style = modifiedStyle; | |
replaceCurrentColor( | |
abstract[0], | |
resolvedColor, | |
resolvedSecondaryColor, | |
resolvedSecondaryOpacity, | |
); | |
return convertCurry(abstract[0]); | |
} | |
const convertCurry = convert.bind(null, React.createElement); | |
function replaceCurrentColor( | |
obj: AbstractElement, | |
primaryColor: string, | |
secondaryColor: string, | |
secondaryOpacity: number, | |
) { | |
obj.children?.forEach((child, _childIndex) => { | |
replaceFill(child, primaryColor, secondaryColor, secondaryOpacity); | |
if (Object.prototype.hasOwnProperty.call(child, 'attributes')) { | |
replaceFill( | |
child.attributes, | |
primaryColor, | |
secondaryColor, | |
secondaryOpacity, | |
); | |
} | |
if (Array.isArray(child.children) && child.children.length > 0) { | |
replaceCurrentColor( | |
child, | |
primaryColor, | |
secondaryColor, | |
secondaryOpacity, | |
); | |
} | |
}); | |
} | |
function replaceFill( | |
obj: any, | |
primaryColor: string, | |
secondaryColor: string, | |
secondaryOpacity: number, | |
) { | |
if (hasPropertySetToValue(obj, 'fill', 'currentColor')) { | |
if (hasPropertySetToValue(obj, 'class', 'fa-primary')) { | |
obj.fill = primaryColor; | |
} else if (hasPropertySetToValue(obj, 'class', 'fa-secondary')) { | |
obj.fill = secondaryColor; | |
obj.fillOpacity = secondaryOpacity; | |
} else { | |
obj.fill = primaryColor; | |
} | |
} | |
} | |
function hasPropertySetToValue(obj: any, property: string, value: string) { | |
return ( | |
Object.prototype.hasOwnProperty.call(obj, property) && | |
obj[property] === value | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment