@ankhorage/surface 3.4.3 → 3.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -2
- package/dist/primitives/icon/PortableIcon.d.ts.map +1 -1
- package/dist/primitives/icon/PortableIcon.js +22 -3
- package/dist/primitives/icon/PortableIcon.js.map +1 -1
- package/package.json +3 -3
- package/src/primitives/icon/PortableIcon.tsx +43 -13
- package/src/primitives/icon/PortableIcon.web.test.tsx +31 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/surface
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Cross-platform UI foundation for React Native and React Native Web. Provides theming, layout primitives, and core building blocks for modern apps.
|
|
9
9
|
|
|
@@ -25,7 +25,6 @@ Cross-platform UI foundation for React Native and React Native Web. Provides the
|
|
|
25
25
|
- [Drawer sequence](././paradox/diagrams/sequences/drawer.mmd)
|
|
26
26
|
- [Field sequence](././paradox/diagrams/sequences/field.mmd)
|
|
27
27
|
- [FontProvider sequence](././paradox/diagrams/sequences/font-provider.mmd)
|
|
28
|
-
- [generatePalette sequence](././paradox/diagrams/sequences/generate-palette.mmd)
|
|
29
28
|
- [Grid sequence](././paradox/diagrams/sequences/grid.mmd)
|
|
30
29
|
- [Icon sequence](././paradox/diagrams/sequences/icon.mmd)
|
|
31
30
|
- [IconButton sequence](././paradox/diagrams/sequences/icon-button.mmd)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortableIcon.d.ts","sourceRoot":"","sources":["../../../src/primitives/icon/PortableIcon.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,+CAA+C,CAAC;AACvD,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC/B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC/B,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAY,KAAK,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC7F,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,yDAAyD,CAAC;AACjE,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,
|
|
1
|
+
{"version":3,"file":"PortableIcon.d.ts","sourceRoot":"","sources":["../../../src/primitives/icon/PortableIcon.tsx"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { FontAwesome6, } from '@react-native-vector-icons/fontawesome6/static';
|
|
|
4
4
|
import { Ionicons } from '@react-native-vector-icons/ionicons/static';
|
|
5
5
|
import { MaterialDesignIcons, } from '@react-native-vector-icons/material-design-icons/static';
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { Image } from 'react-native';
|
|
7
|
+
import { Image, StyleSheet, View, } from 'react-native';
|
|
8
8
|
import { SvgUri } from 'react-native-svg';
|
|
9
9
|
export const SUPPORTED_ICON_PROVIDERS = [
|
|
10
10
|
'Ionicons',
|
|
@@ -13,12 +13,31 @@ export const SUPPORTED_ICON_PROVIDERS = [
|
|
|
13
13
|
'FontAwesome6',
|
|
14
14
|
'MaterialDesignIcons',
|
|
15
15
|
];
|
|
16
|
-
|
|
16
|
+
const styles = StyleSheet.create({
|
|
17
|
+
bundledSvgImage: { height: '100%', width: '100%' },
|
|
18
|
+
});
|
|
19
|
+
/*** Resolve an SVG URI directly or through React Native's native bundled-asset API when available. */
|
|
17
20
|
function resolveSvgIconUri(source) {
|
|
18
21
|
if (typeof source === 'string')
|
|
19
22
|
return source;
|
|
23
|
+
if (!Array.isArray(source) && typeof source === 'object' && typeof source.uri === 'string') {
|
|
24
|
+
return source.uri;
|
|
25
|
+
}
|
|
26
|
+
if (typeof Image.resolveAssetSource !== 'function')
|
|
27
|
+
return null;
|
|
20
28
|
return Image.resolveAssetSource(source).uri;
|
|
21
29
|
}
|
|
30
|
+
/*** Render URI-backed SVGs through SvgUri and let React Native Web resolve bundled numeric assets through Image. */
|
|
31
|
+
function renderSvgIcon(props) {
|
|
32
|
+
const uri = resolveSvgIconUri(props.source);
|
|
33
|
+
if (uri) {
|
|
34
|
+
return (<SvgUri color={props.color} height={props.size} style={props.style} testID={props.testID} uri={uri} width={props.size}/>);
|
|
35
|
+
}
|
|
36
|
+
const imageSource = typeof props.source === 'string' ? { uri: props.source } : props.source;
|
|
37
|
+
return (<View style={[{ height: props.size, width: props.size }, props.style]} testID={props.testID}>
|
|
38
|
+
<Image source={imageSource} style={styles.bundledSvgImage} tintColor={props.color}/>
|
|
39
|
+
</View>);
|
|
40
|
+
}
|
|
22
41
|
function assertNever(value, configuration) {
|
|
23
42
|
throw new Error(`Unsupported icon ${configuration}: ${String(value)}`);
|
|
24
43
|
}
|
|
@@ -50,7 +69,7 @@ function renderFontAwesome6(props, sharedProps) {
|
|
|
50
69
|
}
|
|
51
70
|
export function PortableIcon(props) {
|
|
52
71
|
if ('source' in props) {
|
|
53
|
-
return (
|
|
72
|
+
return renderSvgIcon(props);
|
|
54
73
|
}
|
|
55
74
|
const { provider } = props;
|
|
56
75
|
const sharedProps = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PortableIcon.js","sourceRoot":"","sources":["../../../src/primitives/icon/PortableIcon.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FontAwesome,\n type FontAwesomeIconName,\n} from '@react-native-vector-icons/fontawesome/static';\nimport {\n FontAwesome5,\n type FontAwesome5BrandIconName,\n type FontAwesome5RegularIconName,\n type FontAwesome5SolidIconName,\n} from '@react-native-vector-icons/fontawesome5/static';\nimport {\n FontAwesome6,\n type FontAwesome6BrandIconName,\n type FontAwesome6RegularIconName,\n type FontAwesome6SolidIconName,\n} from '@react-native-vector-icons/fontawesome6/static';\nimport { Ionicons, type IoniconsIconName } from '@react-native-vector-icons/ionicons/static';\nimport {\n MaterialDesignIcons,\n type MaterialDesignIconsIconName,\n} from '@react-native-vector-icons/material-design-icons/static';\nimport React from 'react';\nimport { Image, type StyleProp, type TextStyle, type ViewStyle } from 'react-native';\nimport { SvgUri } from 'react-native-svg';\n\nimport type { SurfaceImageSource } from '../image';\n\nexport const SUPPORTED_ICON_PROVIDERS = [\n 'Ionicons',\n 'FontAwesome',\n 'FontAwesome5',\n 'FontAwesome6',\n 'MaterialDesignIcons',\n] as const;\n\nexport type IconProvider = (typeof SUPPORTED_ICON_PROVIDERS)[number];\nexport type IconVariant = 'regular' | 'solid' | 'brand';\n\nexport type FontIconSource =\n | { name: IoniconsIconName; provider?: 'Ionicons'; variant?: never }\n | { name: FontAwesomeIconName; provider: 'FontAwesome'; variant?: never }\n | { name: FontAwesome5RegularIconName; provider: 'FontAwesome5'; variant: 'regular' }\n | { name: FontAwesome5SolidIconName; provider: 'FontAwesome5'; variant: 'solid' }\n | { name: FontAwesome5BrandIconName; provider: 'FontAwesome5'; variant: 'brand' }\n | { name: FontAwesome6RegularIconName; provider: 'FontAwesome6'; variant: 'regular' }\n | { name: FontAwesome6SolidIconName; provider: 'FontAwesome6'; variant: 'solid' }\n | { name: FontAwesome6BrandIconName; provider: 'FontAwesome6'; variant: 'brand' }\n | { name: MaterialDesignIconsIconName; provider: 'MaterialDesignIcons'; variant?: never };\n\nexport interface SvgIconSource {\n source: SurfaceImageSource;\n name?: never;\n provider?: never;\n variant?: never;\n}\n\nexport type IconSource = FontIconSource | SvgIconSource;\n\ninterface PortableIconPresentationProps {\n color: string;\n size: number;\n testID?: string;\n}\n\ntype FontPortableIconProps = FontIconSource &\n PortableIconPresentationProps & {\n style?: StyleProp<TextStyle>;\n };\n\ntype SvgPortableIconProps = SvgIconSource &\n PortableIconPresentationProps & {\n style?: StyleProp<ViewStyle>;\n };\n\ntype PortableIconProps = FontPortableIconProps | SvgPortableIconProps;\n\ntype SharedIconProps = PortableIconPresentationProps & {\n style?: StyleProp<TextStyle>;\n};\n\n/*** Resolve either a direct URI or a React Native bundled image module to its SVG URI. */\nfunction resolveSvgIconUri(source: SurfaceImageSource): string {\n if (typeof source === 'string') return source;\n\n return Image.resolveAssetSource(source).uri;\n}\n\nfunction assertNever(value: never, configuration: string): never {\n throw new Error(`Unsupported icon ${configuration}: ${String(value)}`);\n}\n\nfunction renderFontAwesome5(\n props: Extract<IconSource, { provider: 'FontAwesome5' }>,\n sharedProps: SharedIconProps,\n) {\n const { variant } = props;\n\n switch (variant) {\n case 'brand':\n return <FontAwesome5 {...sharedProps} iconStyle=\"brand\" name={props.name} />;\n case 'regular':\n return <FontAwesome5 {...sharedProps} iconStyle=\"regular\" name={props.name} />;\n case 'solid':\n return <FontAwesome5 {...sharedProps} iconStyle=\"solid\" name={props.name} />;\n default:\n return assertNever(variant, 'FontAwesome5 variant');\n }\n}\n\nfunction renderFontAwesome6(\n props: Extract<IconSource, { provider: 'FontAwesome6' }>,\n sharedProps: SharedIconProps,\n) {\n const { variant } = props;\n\n switch (variant) {\n case 'brand':\n return <FontAwesome6 {...sharedProps} iconStyle=\"brand\" name={props.name} />;\n case 'regular':\n return <FontAwesome6 {...sharedProps} iconStyle=\"regular\" name={props.name} />;\n case 'solid':\n return <FontAwesome6 {...sharedProps} iconStyle=\"solid\" name={props.name} />;\n default:\n return assertNever(variant, 'FontAwesome6 variant');\n }\n}\n\nexport function PortableIcon(props: PortableIconProps) {\n if ('source' in props) {\n return (\n <SvgUri\n color={props.color}\n height={props.size}\n style={props.style}\n testID={props.testID}\n uri={resolveSvgIconUri(props.source)}\n width={props.size}\n />\n );\n }\n\n const { provider } = props;\n const sharedProps = {\n color: props.color,\n size: props.size,\n style: props.style,\n testID: props.testID,\n };\n\n switch (provider) {\n case undefined:\n case 'Ionicons':\n return <Ionicons {...sharedProps} name={props.name} />;\n case 'FontAwesome':\n return <FontAwesome {...sharedProps} name={props.name} />;\n case 'FontAwesome5':\n return renderFontAwesome5(props, sharedProps);\n case 'FontAwesome6':\n return renderFontAwesome6(props, sharedProps);\n case 'MaterialDesignIcons':\n return <MaterialDesignIcons {...sharedProps} name={props.name} />;\n default:\n return assertNever(provider, 'provider');\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PortableIcon.js","sourceRoot":"","sources":["../../../src/primitives/icon/PortableIcon.tsx"],"names":[],"mappings":"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","sourcesContent":["import {\n FontAwesome,\n type FontAwesomeIconName,\n} from '@react-native-vector-icons/fontawesome/static';\nimport {\n FontAwesome5,\n type FontAwesome5BrandIconName,\n type FontAwesome5RegularIconName,\n type FontAwesome5SolidIconName,\n} from '@react-native-vector-icons/fontawesome5/static';\nimport {\n FontAwesome6,\n type FontAwesome6BrandIconName,\n type FontAwesome6RegularIconName,\n type FontAwesome6SolidIconName,\n} from '@react-native-vector-icons/fontawesome6/static';\nimport { Ionicons, type IoniconsIconName } from '@react-native-vector-icons/ionicons/static';\nimport {\n MaterialDesignIcons,\n type MaterialDesignIconsIconName,\n} from '@react-native-vector-icons/material-design-icons/static';\nimport React from 'react';\nimport {\n Image,\n type StyleProp,\n StyleSheet,\n type TextStyle,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { SvgUri } from 'react-native-svg';\n\nimport type { SurfaceImageSource } from '../image';\n\nexport const SUPPORTED_ICON_PROVIDERS = [\n 'Ionicons',\n 'FontAwesome',\n 'FontAwesome5',\n 'FontAwesome6',\n 'MaterialDesignIcons',\n] as const;\n\nexport type IconProvider = (typeof SUPPORTED_ICON_PROVIDERS)[number];\nexport type IconVariant = 'regular' | 'solid' | 'brand';\n\nexport type FontIconSource =\n | { name: IoniconsIconName; provider?: 'Ionicons'; variant?: never }\n | { name: FontAwesomeIconName; provider: 'FontAwesome'; variant?: never }\n | { name: FontAwesome5RegularIconName; provider: 'FontAwesome5'; variant: 'regular' }\n | { name: FontAwesome5SolidIconName; provider: 'FontAwesome5'; variant: 'solid' }\n | { name: FontAwesome5BrandIconName; provider: 'FontAwesome5'; variant: 'brand' }\n | { name: FontAwesome6RegularIconName; provider: 'FontAwesome6'; variant: 'regular' }\n | { name: FontAwesome6SolidIconName; provider: 'FontAwesome6'; variant: 'solid' }\n | { name: FontAwesome6BrandIconName; provider: 'FontAwesome6'; variant: 'brand' }\n | { name: MaterialDesignIconsIconName; provider: 'MaterialDesignIcons'; variant?: never };\n\nexport interface SvgIconSource {\n source: SurfaceImageSource;\n name?: never;\n provider?: never;\n variant?: never;\n}\n\nexport type IconSource = FontIconSource | SvgIconSource;\n\ninterface PortableIconPresentationProps {\n color: string;\n size: number;\n testID?: string;\n}\n\ntype FontPortableIconProps = FontIconSource &\n PortableIconPresentationProps & {\n style?: StyleProp<TextStyle>;\n };\n\ntype SvgPortableIconProps = SvgIconSource &\n PortableIconPresentationProps & {\n style?: StyleProp<ViewStyle>;\n };\n\ntype PortableIconProps = FontPortableIconProps | SvgPortableIconProps;\n\ntype SharedIconProps = PortableIconPresentationProps & {\n style?: StyleProp<TextStyle>;\n};\n\nconst styles = StyleSheet.create({\n bundledSvgImage: { height: '100%', width: '100%' },\n});\n\n/*** Resolve an SVG URI directly or through React Native's native bundled-asset API when available. */\nfunction resolveSvgIconUri(source: SurfaceImageSource): string | null {\n if (typeof source === 'string') return source;\n if (!Array.isArray(source) && typeof source === 'object' && typeof source.uri === 'string') {\n return source.uri;\n }\n if (typeof Image.resolveAssetSource !== 'function') return null;\n\n return Image.resolveAssetSource(source).uri;\n}\n\n/*** Render URI-backed SVGs through SvgUri and let React Native Web resolve bundled numeric assets through Image. */\nfunction renderSvgIcon(props: SvgPortableIconProps) {\n const uri = resolveSvgIconUri(props.source);\n if (uri) {\n return (\n <SvgUri\n color={props.color}\n height={props.size}\n style={props.style}\n testID={props.testID}\n uri={uri}\n width={props.size}\n />\n );\n }\n\n const imageSource = typeof props.source === 'string' ? { uri: props.source } : props.source;\n return (\n <View style={[{ height: props.size, width: props.size }, props.style]} testID={props.testID}>\n <Image source={imageSource} style={styles.bundledSvgImage} tintColor={props.color} />\n </View>\n );\n}\n\nfunction assertNever(value: never, configuration: string): never {\n throw new Error(`Unsupported icon ${configuration}: ${String(value)}`);\n}\n\nfunction renderFontAwesome5(\n props: Extract<IconSource, { provider: 'FontAwesome5' }>,\n sharedProps: SharedIconProps,\n) {\n const { variant } = props;\n\n switch (variant) {\n case 'brand':\n return <FontAwesome5 {...sharedProps} iconStyle=\"brand\" name={props.name} />;\n case 'regular':\n return <FontAwesome5 {...sharedProps} iconStyle=\"regular\" name={props.name} />;\n case 'solid':\n return <FontAwesome5 {...sharedProps} iconStyle=\"solid\" name={props.name} />;\n default:\n return assertNever(variant, 'FontAwesome5 variant');\n }\n}\n\nfunction renderFontAwesome6(\n props: Extract<IconSource, { provider: 'FontAwesome6' }>,\n sharedProps: SharedIconProps,\n) {\n const { variant } = props;\n\n switch (variant) {\n case 'brand':\n return <FontAwesome6 {...sharedProps} iconStyle=\"brand\" name={props.name} />;\n case 'regular':\n return <FontAwesome6 {...sharedProps} iconStyle=\"regular\" name={props.name} />;\n case 'solid':\n return <FontAwesome6 {...sharedProps} iconStyle=\"solid\" name={props.name} />;\n default:\n return assertNever(variant, 'FontAwesome6 variant');\n }\n}\n\nexport function PortableIcon(props: PortableIconProps) {\n if ('source' in props) {\n return renderSvgIcon(props);\n }\n\n const { provider } = props;\n const sharedProps = {\n color: props.color,\n size: props.size,\n style: props.style,\n testID: props.testID,\n };\n\n switch (provider) {\n case undefined:\n case 'Ionicons':\n return <Ionicons {...sharedProps} name={props.name} />;\n case 'FontAwesome':\n return <FontAwesome {...sharedProps} name={props.name} />;\n case 'FontAwesome5':\n return renderFontAwesome5(props, sharedProps);\n case 'FontAwesome6':\n return renderFontAwesome6(props, sharedProps);\n case 'MaterialDesignIcons':\n return <MaterialDesignIcons {...sharedProps} name={props.name} />;\n default:\n return assertNever(provider, 'provider');\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/surface",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.4.
|
|
4
|
+
"version": "3.4.4",
|
|
5
5
|
"description": "Stable UI foundation for React Native and React Native Web.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/surface#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"version-packages": "ankhorage-changeset version"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
|
-
"@ankhorage/devtools": "^1.11.
|
|
89
|
+
"@ankhorage/devtools": "^1.11.7",
|
|
90
90
|
"@ankhorage/paradox": "^0.1.24",
|
|
91
91
|
"@react-native-vector-icons/fontawesome": "^13.1.3",
|
|
92
92
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"react-native-web": "~0.21.0",
|
|
108
108
|
"typescript": "~6.0.3"
|
|
109
109
|
},
|
|
110
|
-
"packageManager": "bun@1.4.
|
|
110
|
+
"packageManager": "bun@1.4.2"
|
|
111
111
|
}
|
|
@@ -20,7 +20,14 @@ import {
|
|
|
20
20
|
type MaterialDesignIconsIconName,
|
|
21
21
|
} from '@react-native-vector-icons/material-design-icons/static';
|
|
22
22
|
import React from 'react';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
Image,
|
|
25
|
+
type StyleProp,
|
|
26
|
+
StyleSheet,
|
|
27
|
+
type TextStyle,
|
|
28
|
+
View,
|
|
29
|
+
type ViewStyle,
|
|
30
|
+
} from 'react-native';
|
|
24
31
|
import { SvgUri } from 'react-native-svg';
|
|
25
32
|
|
|
26
33
|
import type { SurfaceImageSource } from '../image';
|
|
@@ -78,13 +85,45 @@ type SharedIconProps = PortableIconPresentationProps & {
|
|
|
78
85
|
style?: StyleProp<TextStyle>;
|
|
79
86
|
};
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
const styles = StyleSheet.create({
|
|
89
|
+
bundledSvgImage: { height: '100%', width: '100%' },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/*** Resolve an SVG URI directly or through React Native's native bundled-asset API when available. */
|
|
93
|
+
function resolveSvgIconUri(source: SurfaceImageSource): string | null {
|
|
83
94
|
if (typeof source === 'string') return source;
|
|
95
|
+
if (!Array.isArray(source) && typeof source === 'object' && typeof source.uri === 'string') {
|
|
96
|
+
return source.uri;
|
|
97
|
+
}
|
|
98
|
+
if (typeof Image.resolveAssetSource !== 'function') return null;
|
|
84
99
|
|
|
85
100
|
return Image.resolveAssetSource(source).uri;
|
|
86
101
|
}
|
|
87
102
|
|
|
103
|
+
/*** Render URI-backed SVGs through SvgUri and let React Native Web resolve bundled numeric assets through Image. */
|
|
104
|
+
function renderSvgIcon(props: SvgPortableIconProps) {
|
|
105
|
+
const uri = resolveSvgIconUri(props.source);
|
|
106
|
+
if (uri) {
|
|
107
|
+
return (
|
|
108
|
+
<SvgUri
|
|
109
|
+
color={props.color}
|
|
110
|
+
height={props.size}
|
|
111
|
+
style={props.style}
|
|
112
|
+
testID={props.testID}
|
|
113
|
+
uri={uri}
|
|
114
|
+
width={props.size}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const imageSource = typeof props.source === 'string' ? { uri: props.source } : props.source;
|
|
120
|
+
return (
|
|
121
|
+
<View style={[{ height: props.size, width: props.size }, props.style]} testID={props.testID}>
|
|
122
|
+
<Image source={imageSource} style={styles.bundledSvgImage} tintColor={props.color} />
|
|
123
|
+
</View>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
88
127
|
function assertNever(value: never, configuration: string): never {
|
|
89
128
|
throw new Error(`Unsupported icon ${configuration}: ${String(value)}`);
|
|
90
129
|
}
|
|
@@ -127,16 +166,7 @@ function renderFontAwesome6(
|
|
|
127
166
|
|
|
128
167
|
export function PortableIcon(props: PortableIconProps) {
|
|
129
168
|
if ('source' in props) {
|
|
130
|
-
return (
|
|
131
|
-
<SvgUri
|
|
132
|
-
color={props.color}
|
|
133
|
-
height={props.size}
|
|
134
|
-
style={props.style}
|
|
135
|
-
testID={props.testID}
|
|
136
|
-
uri={resolveSvgIconUri(props.source)}
|
|
137
|
-
width={props.size}
|
|
138
|
-
/>
|
|
139
|
-
);
|
|
169
|
+
return renderSvgIcon(props);
|
|
140
170
|
}
|
|
141
171
|
|
|
142
172
|
const { provider } = props;
|
|
@@ -5,7 +5,25 @@ import React from 'react';
|
|
|
5
5
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
6
6
|
import * as ReactNativeWeb from 'react-native-web';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const WebImageWithoutResolveAssetSource = ({
|
|
9
|
+
source,
|
|
10
|
+
testID,
|
|
11
|
+
tintColor,
|
|
12
|
+
}: {
|
|
13
|
+
source?: unknown;
|
|
14
|
+
testID?: string;
|
|
15
|
+
tintColor?: unknown;
|
|
16
|
+
}) =>
|
|
17
|
+
React.createElement('span', {
|
|
18
|
+
'data-image-source': String(source),
|
|
19
|
+
'data-testid': testID,
|
|
20
|
+
'data-tint-color': String(tintColor),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await mock.module('react-native', () => ({
|
|
24
|
+
...ReactNativeWeb,
|
|
25
|
+
Image: WebImageWithoutResolveAssetSource,
|
|
26
|
+
}));
|
|
9
27
|
await mock.module('react-native-svg', () => ({
|
|
10
28
|
SvgUri: ({ color, height, uri, width }: Record<string, unknown>) =>
|
|
11
29
|
React.createElement('svg', {
|
|
@@ -78,6 +96,18 @@ describe('portable icon rendering', () => {
|
|
|
78
96
|
});
|
|
79
97
|
});
|
|
80
98
|
|
|
99
|
+
describe('portable icon web assets', () => {
|
|
100
|
+
test('renders a bundled SVG asset through React Native Web Image', () => {
|
|
101
|
+
const markup = renderToStaticMarkup(
|
|
102
|
+
<Icon color="#123456" size={18} source={42} testID="bundled-svg" />,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(markup).toContain('data-image-source="42"');
|
|
106
|
+
expect(markup).toContain('data-testid="bundled-svg"');
|
|
107
|
+
expect(markup).toContain('data-tint-color="#123456"');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
81
111
|
describe('portable icon contract', () => {
|
|
82
112
|
test('publishes the intentional provider inventory', () => {
|
|
83
113
|
expect(SUPPORTED_ICON_PROVIDERS.length).toBeGreaterThan(0);
|