import { FontAwesome } from '@react-native-vector-icons/fontawesome/static'; import { FontAwesome5 } from '@react-native-vector-icons/fontawesome5/static'; import { FontAwesome6 } from '@react-native-vector-icons/fontawesome6/static'; import { Ionicons } from '@react-native-vector-icons/ionicons/static'; import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons/static'; import React from 'react'; import { Image, type ImageURISource, type StyleProp, StyleSheet, type TextStyle, View, type ViewStyle, } from 'react-native'; import { SvgUri } from 'react-native-svg'; import type { FontIconSource, IconSource, SvgIconSource } from '../../../../types/icon'; import type { SurfaceImageSource } from '../../../../types/image'; /*** Renders a resolved portable icon using the configured font or SVG runtime. */ export function PortableIcon(props: PortableIconProps) { if ('source' in props) return renderSvgIcon(props); const { provider } = props; const sharedProps = { color: props.color, size: props.size, style: props.style, testID: props.testID, }; switch (provider) { case undefined: case 'Ionicons': return ; case 'FontAwesome': return ; case 'FontAwesome5': return renderFontAwesome5(props, sharedProps); case 'FontAwesome6': return renderFontAwesome6(props, sharedProps); case 'MaterialDesignIcons': return ; default: return assertNever(provider, 'provider'); } } interface PortableIconPresentationProps { color: string; size: number; testID?: string; } type FontPortableIconProps = FontIconSource & PortableIconPresentationProps & { style?: StyleProp }; type SvgPortableIconProps = SvgIconSource & PortableIconPresentationProps & { style?: StyleProp }; type PortableIconProps = FontPortableIconProps | SvgPortableIconProps; type SharedIconProps = PortableIconPresentationProps & { style?: StyleProp }; const styles = StyleSheet.create({ bundledSvgImage: { height: '100%', width: '100%' } }); /*** Resolves an SVG URI directly or through React Native's bundled-asset API. */ function resolveSvgIconUri(source: SurfaceImageSource): string | null { if (typeof source === 'string') return source; if (isImageUriSource(source) && typeof source.uri === 'string') return source.uri; if (typeof Image.resolveAssetSource !== 'function') return null; return Image.resolveAssetSource(source)?.uri ?? null; } /*** Narrows a React Native image source to one URI-bearing source object. */ function isImageUriSource(source: Exclude): source is ImageURISource { return typeof source === 'object' && 'uri' in source; } /*** Renders URI-backed SVGs and bundled SVG image assets. */ function renderSvgIcon(props: SvgPortableIconProps) { const uri = resolveSvgIconUri(props.source); if (uri) { return ( ); } const imageSource = typeof props.source === 'string' ? { uri: props.source } : props.source; return ( ); } /*** Rejects unsupported icon configuration at runtime. */ function assertNever(value: never, configuration: string): never { throw new Error(`Unsupported icon ${configuration}: ${String(value)}`); } /*** Renders one FontAwesome5 variant. */ function renderFontAwesome5( props: Extract, sharedProps: SharedIconProps, ) { const { variant } = props; switch (variant) { case 'brand': return ; case 'regular': return ; case 'solid': return ; default: return assertNever(variant, 'FontAwesome5 variant'); } } /*** Renders one FontAwesome6 variant. */ function renderFontAwesome6( props: Extract, sharedProps: SharedIconProps, ) { const { variant } = props; switch (variant) { case 'brand': return ; case 'regular': return ; case 'solid': return ; default: return assertNever(variant, 'FontAwesome6 variant'); } }