@ankhorage/surface 2.2.1 → 3.0.1
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 +12 -0
- package/README.md +1 -2
- package/dist/components/action-sheet/ActionSheet.d.ts.map +1 -1
- package/dist/components/action-sheet/ActionSheet.js +12 -7
- package/dist/components/action-sheet/ActionSheet.js.map +1 -1
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +5 -7
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/button/types.d.ts +2 -5
- package/dist/components/button/types.d.ts.map +1 -1
- package/dist/components/button/types.js.map +1 -1
- package/dist/components/drawer/Drawer.d.ts.map +1 -1
- package/dist/components/drawer/Drawer.js +12 -7
- package/dist/components/drawer/Drawer.js.map +1 -1
- package/dist/components/icon-button/IconButton.d.ts.map +1 -1
- package/dist/components/icon-button/IconButton.js +1 -1
- package/dist/components/icon-button/IconButton.js.map +1 -1
- package/dist/components/icon-button/types.d.ts +2 -5
- package/dist/components/icon-button/types.d.ts.map +1 -1
- package/dist/components/icon-button/types.js.map +1 -1
- package/dist/components/menu/Menu.d.ts.map +1 -1
- package/dist/components/menu/Menu.js +12 -7
- package/dist/components/menu/Menu.js.map +1 -1
- package/dist/components/modal/Modal.d.ts.map +1 -1
- package/dist/components/modal/Modal.js +12 -7
- package/dist/components/modal/Modal.js.map +1 -1
- package/dist/components/navigation/NavigationItem.d.ts.map +1 -1
- package/dist/components/navigation/NavigationItem.js +1 -1
- package/dist/components/navigation/NavigationItem.js.map +1 -1
- package/dist/components/navigation/TabBarItem.d.ts.map +1 -1
- package/dist/components/navigation/TabBarItem.js +1 -1
- package/dist/components/navigation/TabBarItem.js.map +1 -1
- package/dist/components/navigation/types.d.ts +2 -2
- package/dist/components/navigation/types.d.ts.map +1 -1
- package/dist/components/navigation/types.js.map +1 -1
- package/dist/components/toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/toast/ToastProvider.js +3 -2
- package/dist/components/toast/ToastProvider.js.map +1 -1
- package/dist/components/tooltip/Tooltip.d.ts.map +1 -1
- package/dist/components/tooltip/Tooltip.js +10 -5
- package/dist/components/tooltip/Tooltip.js.map +1 -1
- package/dist/context/TranslationContext.d.ts.map +1 -1
- package/dist/context/TranslationContext.js +3 -3
- package/dist/context/TranslationContext.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/overlay/OverlayProvider.d.ts.map +1 -1
- package/dist/internal/overlay/OverlayProvider.js +17 -16
- package/dist/internal/overlay/OverlayProvider.js.map +1 -1
- package/dist/internal/overlay/Portal.d.ts.map +1 -1
- package/dist/internal/overlay/Portal.js +2 -1
- package/dist/internal/overlay/Portal.js.map +1 -1
- package/dist/internal/resolvePointerEvents.d.ts +9 -0
- package/dist/internal/resolvePointerEvents.d.ts.map +1 -0
- package/dist/internal/resolvePointerEvents.js +17 -0
- package/dist/internal/resolvePointerEvents.js.map +1 -0
- package/dist/layout/AppBar.js +1 -1
- package/dist/layout/AppBar.js.map +1 -1
- package/dist/layout/Box.d.ts.map +1 -1
- package/dist/layout/Box.js +4 -22
- package/dist/layout/Box.js.map +1 -1
- package/dist/layout/Grid.js.map +1 -1
- package/dist/primitives/icon/Icon.d.ts +5 -12
- package/dist/primitives/icon/Icon.d.ts.map +1 -1
- package/dist/primitives/icon/Icon.js +5 -11
- package/dist/primitives/icon/Icon.js.map +1 -1
- package/dist/primitives/icon/PortableIcon.d.ts +51 -0
- package/dist/primitives/icon/PortableIcon.d.ts.map +1 -0
- package/dist/primitives/icon/PortableIcon.js +64 -0
- package/dist/primitives/icon/PortableIcon.js.map +1 -0
- package/dist/primitives/icon/index.d.ts +2 -1
- package/dist/primitives/icon/index.d.ts.map +1 -1
- package/dist/primitives/icon/index.js +1 -0
- package/dist/primitives/icon/index.js.map +1 -1
- package/dist/primitives/image/Image.d.ts.map +1 -1
- package/dist/primitives/image/Image.js +3 -5
- package/dist/primitives/image/Image.js.map +1 -1
- package/package.json +30 -27
- package/src/components/action-sheet/ActionSheet.tsx +14 -8
- package/src/components/button/Button.tsx +5 -17
- package/src/components/button/types.ts +2 -5
- package/src/components/drawer/Drawer.tsx +14 -8
- package/src/components/icon-button/IconButton.tsx +1 -6
- package/src/components/icon-button/types.ts +2 -5
- package/src/components/menu/Menu.tsx +14 -8
- package/src/components/modal/Modal.tsx +14 -8
- package/src/components/navigation/NavigationItem.tsx +1 -6
- package/src/components/navigation/TabBarItem.tsx +1 -6
- package/src/components/navigation/types.ts +2 -2
- package/src/components/toast/ToastProvider.tsx +3 -2
- package/src/components/tooltip/Tooltip.tsx +12 -6
- package/src/context/TranslationContext.tsx +12 -15
- package/src/index.test.ts +8 -0
- package/src/index.ts +2 -2
- package/src/internal/overlay/OverlayProvider.tsx +20 -20
- package/src/internal/overlay/Portal.tsx +2 -1
- package/src/internal/resolvePointerEvents.test.ts +19 -0
- package/src/internal/resolvePointerEvents.ts +29 -0
- package/src/layout/AppBar.tsx +1 -1
- package/src/layout/Box.tsx +5 -36
- package/src/layout/Grid.tsx +3 -3
- package/src/primitives/icon/Icon.tsx +9 -22
- package/src/primitives/icon/PortableIcon.tsx +112 -0
- package/src/primitives/icon/PortableIcon.web.test.tsx +119 -0
- package/src/primitives/icon/index.ts +2 -1
- package/src/primitives/image/Image.tsx +3 -6
- package/src/theme/colorEngine.test.ts +2 -2
- package/src/webAcceptance.test.tsx +84 -0
- package/dist/primitives/icon/resolveExpoIconComponent.d.ts +0 -11
- package/dist/primitives/icon/resolveExpoIconComponent.d.ts.map +0 -1
- package/dist/primitives/icon/resolveExpoIconComponent.js +0 -20
- package/dist/primitives/icon/resolveExpoIconComponent.js.map +0 -1
- package/src/primitives/icon/resolveExpoIconComponent.test.ts +0 -45
- package/src/primitives/icon/resolveExpoIconComponent.ts +0 -33
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FontAwesome,
|
|
3
|
+
type FontAwesomeIconName,
|
|
4
|
+
} from '@react-native-vector-icons/fontawesome/static';
|
|
5
|
+
import {
|
|
6
|
+
FontAwesome5,
|
|
7
|
+
type FontAwesome5BrandIconName,
|
|
8
|
+
type FontAwesome5RegularIconName,
|
|
9
|
+
type FontAwesome5SolidIconName,
|
|
10
|
+
} from '@react-native-vector-icons/fontawesome5/static';
|
|
11
|
+
import {
|
|
12
|
+
FontAwesome6,
|
|
13
|
+
type FontAwesome6BrandIconName,
|
|
14
|
+
type FontAwesome6RegularIconName,
|
|
15
|
+
type FontAwesome6SolidIconName,
|
|
16
|
+
} from '@react-native-vector-icons/fontawesome6/static';
|
|
17
|
+
import { Ionicons, type IoniconsIconName } from '@react-native-vector-icons/ionicons/static';
|
|
18
|
+
import React from 'react';
|
|
19
|
+
import { type StyleProp, type TextStyle } from 'react-native';
|
|
20
|
+
|
|
21
|
+
export const SUPPORTED_ICON_PROVIDERS = [
|
|
22
|
+
'Ionicons',
|
|
23
|
+
'FontAwesome',
|
|
24
|
+
'FontAwesome5',
|
|
25
|
+
'FontAwesome6',
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
export type IconProvider = (typeof SUPPORTED_ICON_PROVIDERS)[number];
|
|
29
|
+
export type IconVariant = 'regular' | 'solid' | 'brand';
|
|
30
|
+
|
|
31
|
+
export type IconSource =
|
|
32
|
+
| { name: IoniconsIconName; provider?: 'Ionicons'; variant?: never }
|
|
33
|
+
| { name: FontAwesomeIconName; provider: 'FontAwesome'; variant?: never }
|
|
34
|
+
| { name: FontAwesome5RegularIconName; provider: 'FontAwesome5'; variant: 'regular' }
|
|
35
|
+
| { name: FontAwesome5SolidIconName; provider: 'FontAwesome5'; variant: 'solid' }
|
|
36
|
+
| { name: FontAwesome5BrandIconName; provider: 'FontAwesome5'; variant: 'brand' }
|
|
37
|
+
| { name: FontAwesome6RegularIconName; provider: 'FontAwesome6'; variant: 'regular' }
|
|
38
|
+
| { name: FontAwesome6SolidIconName; provider: 'FontAwesome6'; variant: 'solid' }
|
|
39
|
+
| { name: FontAwesome6BrandIconName; provider: 'FontAwesome6'; variant: 'brand' };
|
|
40
|
+
|
|
41
|
+
type PortableIconProps = IconSource & {
|
|
42
|
+
color: string;
|
|
43
|
+
size: number;
|
|
44
|
+
style?: StyleProp<TextStyle>;
|
|
45
|
+
testID?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type SharedIconProps = Pick<PortableIconProps, 'color' | 'size' | 'style' | 'testID'>;
|
|
49
|
+
|
|
50
|
+
function assertNever(value: never, configuration: string): never {
|
|
51
|
+
throw new Error(`Unsupported icon ${configuration}: ${String(value)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderFontAwesome5(
|
|
55
|
+
props: Extract<IconSource, { provider: 'FontAwesome5' }>,
|
|
56
|
+
sharedProps: SharedIconProps,
|
|
57
|
+
) {
|
|
58
|
+
const { variant } = props;
|
|
59
|
+
|
|
60
|
+
switch (variant) {
|
|
61
|
+
case 'brand':
|
|
62
|
+
return <FontAwesome5 {...sharedProps} iconStyle="brand" name={props.name} />;
|
|
63
|
+
case 'regular':
|
|
64
|
+
return <FontAwesome5 {...sharedProps} iconStyle="regular" name={props.name} />;
|
|
65
|
+
case 'solid':
|
|
66
|
+
return <FontAwesome5 {...sharedProps} iconStyle="solid" name={props.name} />;
|
|
67
|
+
default:
|
|
68
|
+
return assertNever(variant, 'FontAwesome5 variant');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function renderFontAwesome6(
|
|
73
|
+
props: Extract<IconSource, { provider: 'FontAwesome6' }>,
|
|
74
|
+
sharedProps: SharedIconProps,
|
|
75
|
+
) {
|
|
76
|
+
const { variant } = props;
|
|
77
|
+
|
|
78
|
+
switch (variant) {
|
|
79
|
+
case 'brand':
|
|
80
|
+
return <FontAwesome6 {...sharedProps} iconStyle="brand" name={props.name} />;
|
|
81
|
+
case 'regular':
|
|
82
|
+
return <FontAwesome6 {...sharedProps} iconStyle="regular" name={props.name} />;
|
|
83
|
+
case 'solid':
|
|
84
|
+
return <FontAwesome6 {...sharedProps} iconStyle="solid" name={props.name} />;
|
|
85
|
+
default:
|
|
86
|
+
return assertNever(variant, 'FontAwesome6 variant');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function PortableIcon(props: PortableIconProps) {
|
|
91
|
+
const { provider } = props;
|
|
92
|
+
const sharedProps = {
|
|
93
|
+
color: props.color,
|
|
94
|
+
size: props.size,
|
|
95
|
+
style: props.style,
|
|
96
|
+
testID: props.testID,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
switch (provider) {
|
|
100
|
+
case undefined:
|
|
101
|
+
case 'Ionicons':
|
|
102
|
+
return <Ionicons {...sharedProps} name={props.name} />;
|
|
103
|
+
case 'FontAwesome':
|
|
104
|
+
return <FontAwesome {...sharedProps} name={props.name} />;
|
|
105
|
+
case 'FontAwesome5':
|
|
106
|
+
return renderFontAwesome5(props, sharedProps);
|
|
107
|
+
case 'FontAwesome6':
|
|
108
|
+
return renderFontAwesome6(props, sharedProps);
|
|
109
|
+
default:
|
|
110
|
+
return assertNever(provider, 'provider');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { describe, expect, mock, test } from 'bun:test';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
6
|
+
import * as ReactNativeWeb from 'react-native-web';
|
|
7
|
+
|
|
8
|
+
await mock.module('react-native', () => ReactNativeWeb);
|
|
9
|
+
|
|
10
|
+
const { Icon } = await import('./Icon');
|
|
11
|
+
const { PortableIcon, SUPPORTED_ICON_PROVIDERS } = await import('./PortableIcon');
|
|
12
|
+
|
|
13
|
+
describe('portable icon rendering', () => {
|
|
14
|
+
test('renders Ionicons through the public Icon API', () => {
|
|
15
|
+
const markup = renderToStaticMarkup(<Icon color="#123456" name="home-outline" size={18} />);
|
|
16
|
+
|
|
17
|
+
expect(markup).toContain('font-family:Ionicons');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('renders FontAwesome through the public Icon API', () => {
|
|
21
|
+
const markup = renderToStaticMarkup(
|
|
22
|
+
<Icon color="#123456" name="github" provider="FontAwesome" size={18} />,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(markup).toContain('font-family:FontAwesome');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('renders a FontAwesome5 brand icon through its brand font', () => {
|
|
29
|
+
const markup = renderToStaticMarkup(
|
|
30
|
+
<Icon color="#123456" name="microsoft" provider="FontAwesome5" size={18} variant="brand" />,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(markup).toContain('font-family:FontAwesome5Brands-Regular');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('renders a FontAwesome5 solid icon through its solid font', () => {
|
|
37
|
+
const markup = renderToStaticMarkup(
|
|
38
|
+
<Icon color="#123456" name="video" provider="FontAwesome5" size={18} variant="solid" />,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(markup).toContain('font-family:FontAwesome5Free-Solid');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('renders a FontAwesome6 brand icon through its brand font', () => {
|
|
45
|
+
const markup = renderToStaticMarkup(
|
|
46
|
+
<Icon color="#123456" name="x-twitter" provider="FontAwesome6" size={18} variant="brand" />,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
expect(markup).toContain('font-family:FontAwesome6Brands-Regular');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('portable icon contract', () => {
|
|
54
|
+
test('publishes the intentional provider inventory', () => {
|
|
55
|
+
expect(SUPPORTED_ICON_PROVIDERS).toEqual([
|
|
56
|
+
'Ionicons',
|
|
57
|
+
'FontAwesome',
|
|
58
|
+
'FontAwesome5',
|
|
59
|
+
'FontAwesome6',
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('portable icon runtime rejection', () => {
|
|
65
|
+
test('throws for an invalid runtime provider instead of falling back to Ionicons', () => {
|
|
66
|
+
const props = {
|
|
67
|
+
color: '#123456',
|
|
68
|
+
name: 'home-outline',
|
|
69
|
+
size: 18,
|
|
70
|
+
} satisfies Parameters<typeof PortableIcon>[0];
|
|
71
|
+
Object.defineProperty(props, 'provider', { enumerable: true, value: 'UnknownIcons' });
|
|
72
|
+
|
|
73
|
+
expect(() => renderToStaticMarkup(<PortableIcon {...props} />)).toThrow(
|
|
74
|
+
'Unsupported icon provider: UnknownIcons',
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('throws for an invalid runtime FontAwesome5 variant', () => {
|
|
79
|
+
const props = {
|
|
80
|
+
color: '#123456',
|
|
81
|
+
name: 'video',
|
|
82
|
+
provider: 'FontAwesome5',
|
|
83
|
+
size: 18,
|
|
84
|
+
variant: 'solid',
|
|
85
|
+
} satisfies Parameters<typeof PortableIcon>[0];
|
|
86
|
+
Object.defineProperty(props, 'variant', { value: 'duotone' });
|
|
87
|
+
|
|
88
|
+
expect(() => renderToStaticMarkup(<PortableIcon {...props} />)).toThrow(
|
|
89
|
+
'Unsupported icon FontAwesome5 variant: duotone',
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('throws for an invalid runtime FontAwesome6 variant', () => {
|
|
94
|
+
const props = {
|
|
95
|
+
color: '#123456',
|
|
96
|
+
name: 'x-twitter',
|
|
97
|
+
provider: 'FontAwesome6',
|
|
98
|
+
size: 18,
|
|
99
|
+
variant: 'brand',
|
|
100
|
+
} satisfies Parameters<typeof PortableIcon>[0];
|
|
101
|
+
Object.defineProperty(props, 'variant', { value: 'duotone' });
|
|
102
|
+
|
|
103
|
+
expect(() => renderToStaticMarkup(<PortableIcon {...props} />)).toThrow(
|
|
104
|
+
'Unsupported icon FontAwesome6 variant: duotone',
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('portable icon dependency boundary', () => {
|
|
110
|
+
test('has no Expo icon dependency or resolver path', () => {
|
|
111
|
+
const packageSource = readFileSync(new URL('../../../package.json', import.meta.url), 'utf8');
|
|
112
|
+
const iconSource = readFileSync(new URL('./PortableIcon.tsx', import.meta.url), 'utf8');
|
|
113
|
+
|
|
114
|
+
expect(packageSource).not.toContain('@expo/vector-icons');
|
|
115
|
+
expect(packageSource).not.toContain('expo-font');
|
|
116
|
+
expect(iconSource).not.toContain('resolveExpoIconComponent');
|
|
117
|
+
expect(iconSource).not.toContain("from 'expo");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -29,12 +29,9 @@ export function Image({
|
|
|
29
29
|
onError,
|
|
30
30
|
}: ImageProps) {
|
|
31
31
|
const { theme } = useTheme();
|
|
32
|
-
const [showFallback, setShowFallback] = React.useState(false);
|
|
33
32
|
const primaryKey = React.useMemo(() => getSourceKey(source), [source]);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
setShowFallback(false);
|
|
37
|
-
}, [primaryKey]);
|
|
33
|
+
const [failedPrimaryKey, setFailedPrimaryKey] = React.useState<string | null>(null);
|
|
34
|
+
const showFallback = primaryKey !== null && failedPrimaryKey === primaryKey;
|
|
38
35
|
|
|
39
36
|
const normalizedPrimary = normalizeSource(source);
|
|
40
37
|
const normalizedFallback = normalizeSource(fallbackSource);
|
|
@@ -56,7 +53,7 @@ export function Image({
|
|
|
56
53
|
onError?.(event);
|
|
57
54
|
|
|
58
55
|
if (!showFallback && normalizedFallback) {
|
|
59
|
-
|
|
56
|
+
setFailedPrimaryKey(primaryKey);
|
|
60
57
|
}
|
|
61
58
|
}}
|
|
62
59
|
resizeMode={resolvedResizeMode}
|
|
@@ -105,12 +105,12 @@ describe('colorEngine', () => {
|
|
|
105
105
|
|
|
106
106
|
it('preserves the primary color at swatch step 500 in light mode', () => {
|
|
107
107
|
const { swatches } = generatePalette(mockConfig, 'light');
|
|
108
|
-
expect(swatches.primary[500]).toBe(mockConfig.light.primaryColor);
|
|
108
|
+
expect(String(swatches.primary[500])).toBe(mockConfig.light.primaryColor);
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
it('preserves the primary color at swatch step 500 in dark mode', () => {
|
|
112
112
|
const { swatches } = generatePalette(mockConfig, 'dark');
|
|
113
|
-
expect(swatches.primary[500]).toBe(mockConfig.dark.primaryColor);
|
|
113
|
+
expect(String(swatches.primary[500])).toBe(mockConfig.dark.primaryColor);
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
it('provides a neutral swatch with neutralKeyColor at step 500', () => {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect, mock, test } from 'bun:test';
|
|
2
|
+
import { Window } from 'happy-dom';
|
|
3
|
+
import React, { act } from 'react';
|
|
4
|
+
import { renderToString } from 'react-dom/server';
|
|
5
|
+
import * as ReactNativeWeb from 'react-native-web';
|
|
6
|
+
|
|
7
|
+
await mock.module('react-native', () => ReactNativeWeb);
|
|
8
|
+
|
|
9
|
+
const scrollContentStyle = { padding: 4 };
|
|
10
|
+
|
|
11
|
+
const { Container } = await import('./layout/Container');
|
|
12
|
+
const { Grid } = await import('./layout/Grid');
|
|
13
|
+
const { ScrollArea } = await import('./layout/ScrollArea');
|
|
14
|
+
const { Show } = await import('./layout/Show');
|
|
15
|
+
const { ThemeProvider } = await import('./theme/ThemeContext');
|
|
16
|
+
|
|
17
|
+
function ResponsiveAcceptanceTree() {
|
|
18
|
+
return (
|
|
19
|
+
<ThemeProvider>
|
|
20
|
+
<Container maxWidth={{ base: 640, md: 960 }} px={{ base: 12, md: 24 }} testID="container">
|
|
21
|
+
<ScrollArea contentContainerStyle={scrollContentStyle} testID="scroll-area">
|
|
22
|
+
<Grid cols={{ base: 1, md: 2 }} gap={{ base: 8, md: 16 }} testID="grid">
|
|
23
|
+
<ReactNativeWeb.View testID="grid-first" />
|
|
24
|
+
<ReactNativeWeb.View testID="grid-second" />
|
|
25
|
+
</Grid>
|
|
26
|
+
<Show
|
|
27
|
+
fallback={<ReactNativeWeb.Text>static-fallback</ReactNativeWeb.Text>}
|
|
28
|
+
when={{ base: false, md: true }}
|
|
29
|
+
>
|
|
30
|
+
<ReactNativeWeb.Text>wide-content</ReactNativeWeb.Text>
|
|
31
|
+
</Show>
|
|
32
|
+
</ScrollArea>
|
|
33
|
+
</Container>
|
|
34
|
+
</ThemeProvider>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test('RN Web 0.21 statically renders and hydrates representative responsive primitives', async () => {
|
|
39
|
+
const markup = renderToString(<ResponsiveAcceptanceTree />);
|
|
40
|
+
|
|
41
|
+
expect(markup).toContain('data-testid="container"');
|
|
42
|
+
expect(markup).toContain('data-testid="scroll-area"');
|
|
43
|
+
expect(markup).toContain('data-testid="grid"');
|
|
44
|
+
expect(markup).toContain('static-fallback');
|
|
45
|
+
expect(markup).not.toContain('wide-content');
|
|
46
|
+
|
|
47
|
+
const browserWindow = new Window({ url: 'https://surface.test/' });
|
|
48
|
+
Object.assign(globalThis, {
|
|
49
|
+
IS_REACT_ACT_ENVIRONMENT: true,
|
|
50
|
+
Node: browserWindow.Node,
|
|
51
|
+
document: browserWindow.document,
|
|
52
|
+
navigator: browserWindow.navigator,
|
|
53
|
+
window: browserWindow,
|
|
54
|
+
});
|
|
55
|
+
const container = browserWindow.document.createElement('div');
|
|
56
|
+
container.innerHTML = markup;
|
|
57
|
+
browserWindow.document.body.append(container);
|
|
58
|
+
const hydrationErrors: string[] = [];
|
|
59
|
+
const originalError = console.error;
|
|
60
|
+
console.error = (...values: unknown[]) => {
|
|
61
|
+
hydrationErrors.push(values.map(String).join(' '));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const { hydrateRoot } = await import('react-dom/client');
|
|
66
|
+
const root = hydrateRoot(container as unknown as Element, <ResponsiveAcceptanceTree />);
|
|
67
|
+
await act(async () => Promise.resolve());
|
|
68
|
+
|
|
69
|
+
expect(container.querySelector('[data-testid="container"]')).not.toBeNull();
|
|
70
|
+
expect(container.querySelector('[data-testid="scroll-area"]')).not.toBeNull();
|
|
71
|
+
expect(container.querySelectorAll('[data-testid^="grid-"]')).toHaveLength(2);
|
|
72
|
+
expect(hydrationErrors).toEqual([]);
|
|
73
|
+
|
|
74
|
+
act(() => root.unmount());
|
|
75
|
+
} finally {
|
|
76
|
+
console.error = originalError;
|
|
77
|
+
browserWindow.close();
|
|
78
|
+
Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT');
|
|
79
|
+
Reflect.deleteProperty(globalThis, 'Node');
|
|
80
|
+
Reflect.deleteProperty(globalThis, 'document');
|
|
81
|
+
Reflect.deleteProperty(globalThis, 'navigator');
|
|
82
|
+
Reflect.deleteProperty(globalThis, 'window');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
import { type StyleProp, type TextStyle } from 'react-native';
|
|
3
|
-
export type ExpoIconComponent = React.ElementType<{
|
|
4
|
-
color?: string;
|
|
5
|
-
name?: string;
|
|
6
|
-
size?: number;
|
|
7
|
-
style?: StyleProp<TextStyle>;
|
|
8
|
-
testID?: string;
|
|
9
|
-
}>;
|
|
10
|
-
export declare function resolveExpoIconComponent(provider: string): ExpoIconComponent;
|
|
11
|
-
//# sourceMappingURL=resolveExpoIconComponent.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolveExpoIconComponent.d.ts","sourceRoot":"","sources":["../../../src/primitives/icon/resolveExpoIconComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,MAAM,iBAAiB,GAAG,KAAK,CAAC,WAAW,CAAC;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AAMH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAQ5E"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import * as ExpoIcons from '@expo/vector-icons';
|
|
2
|
-
import {} from 'react-native';
|
|
3
|
-
const EXPO_ICON_PROVIDER_ALIASES = {
|
|
4
|
-
'material-community': 'MaterialCommunityIcons',
|
|
5
|
-
};
|
|
6
|
-
export function resolveExpoIconComponent(provider) {
|
|
7
|
-
const normalizedProvider = resolveExpoIconProviderName(provider);
|
|
8
|
-
const candidate = ExpoIcons[normalizedProvider];
|
|
9
|
-
if (typeof candidate === 'function') {
|
|
10
|
-
return candidate;
|
|
11
|
-
}
|
|
12
|
-
return ExpoIcons.Ionicons;
|
|
13
|
-
}
|
|
14
|
-
function resolveExpoIconProviderName(provider) {
|
|
15
|
-
const normalizedProvider = provider.trim().toLowerCase();
|
|
16
|
-
return normalizedProvider in EXPO_ICON_PROVIDER_ALIASES
|
|
17
|
-
? EXPO_ICON_PROVIDER_ALIASES[normalizedProvider]
|
|
18
|
-
: provider;
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=resolveExpoIconComponent.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resolveExpoIconComponent.js","sourceRoot":"","sources":["../../../src/primitives/icon/resolveExpoIconComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAEhD,OAAO,EAAkC,MAAM,cAAc,CAAC;AAU9D,MAAM,0BAA0B,GAAG;IACjC,oBAAoB,EAAE,wBAAwB;CACtC,CAAC;AAEX,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,kBAAkB,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IACjE,MAAM,SAAS,GAAI,SAAqC,CAAC,kBAAkB,CAAC,CAAC;IAC7E,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,SAA8B,CAAC;IACxC,CAAC;IAED,OAAO,SAAS,CAAC,QAA6B,CAAC;AACjD,CAAC;AAED,SAAS,2BAA2B,CAAC,QAAgB;IACnD,MAAM,kBAAkB,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEzD,OAAO,kBAAkB,IAAI,0BAA0B;QACrD,CAAC,CAAC,0BAA0B,CAAC,kBAA6D,CAAC;QAC3F,CAAC,CAAC,QAAQ,CAAC;AACf,CAAC","sourcesContent":["import * as ExpoIcons from '@expo/vector-icons';\nimport type React from 'react';\nimport { type StyleProp, type TextStyle } from 'react-native';\n\nexport type ExpoIconComponent = React.ElementType<{\n color?: string;\n name?: string;\n size?: number;\n style?: StyleProp<TextStyle>;\n testID?: string;\n}>;\n\nconst EXPO_ICON_PROVIDER_ALIASES = {\n 'material-community': 'MaterialCommunityIcons',\n} as const;\n\nexport function resolveExpoIconComponent(provider: string): ExpoIconComponent {\n const normalizedProvider = resolveExpoIconProviderName(provider);\n const candidate = (ExpoIcons as Record<string, unknown>)[normalizedProvider];\n if (typeof candidate === 'function') {\n return candidate as ExpoIconComponent;\n }\n\n return ExpoIcons.Ionicons as ExpoIconComponent;\n}\n\nfunction resolveExpoIconProviderName(provider: string): string {\n const normalizedProvider = provider.trim().toLowerCase();\n\n return normalizedProvider in EXPO_ICON_PROVIDER_ALIASES\n ? EXPO_ICON_PROVIDER_ALIASES[normalizedProvider as keyof typeof EXPO_ICON_PROVIDER_ALIASES]\n : provider;\n}\n"]}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
const Ionicons = () => null;
|
|
4
|
-
const MaterialIcons = () => null;
|
|
5
|
-
const MaterialCommunityIcons = () => null;
|
|
6
|
-
|
|
7
|
-
describe('resolveExpoIconComponent', () => {
|
|
8
|
-
test('returns the requested Expo icon family when it exists', async () => {
|
|
9
|
-
await mock.module('@expo/vector-icons', () => ({
|
|
10
|
-
Ionicons,
|
|
11
|
-
MaterialCommunityIcons,
|
|
12
|
-
MaterialIcons,
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
const { resolveExpoIconComponent } = await import('./resolveExpoIconComponent');
|
|
16
|
-
|
|
17
|
-
expect(resolveExpoIconComponent('Ionicons')).toBe(Ionicons);
|
|
18
|
-
expect(resolveExpoIconComponent('MaterialIcons')).toBe(MaterialIcons);
|
|
19
|
-
expect(resolveExpoIconComponent('MaterialCommunityIcons')).toBe(MaterialCommunityIcons);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('resolves the canonical material-community provider id', async () => {
|
|
23
|
-
await mock.module('@expo/vector-icons', () => ({
|
|
24
|
-
Ionicons,
|
|
25
|
-
MaterialCommunityIcons,
|
|
26
|
-
MaterialIcons,
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
const { resolveExpoIconComponent } = await import('./resolveExpoIconComponent');
|
|
30
|
-
|
|
31
|
-
expect(resolveExpoIconComponent('material-community')).toBe(MaterialCommunityIcons);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('falls back to Ionicons when the provider is unknown', async () => {
|
|
35
|
-
await mock.module('@expo/vector-icons', () => ({
|
|
36
|
-
Ionicons,
|
|
37
|
-
MaterialCommunityIcons,
|
|
38
|
-
MaterialIcons,
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
const { resolveExpoIconComponent } = await import('./resolveExpoIconComponent');
|
|
42
|
-
|
|
43
|
-
expect(resolveExpoIconComponent('MissingIconFamily')).toBe(Ionicons);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as ExpoIcons from '@expo/vector-icons';
|
|
2
|
-
import type React from 'react';
|
|
3
|
-
import { type StyleProp, type TextStyle } from 'react-native';
|
|
4
|
-
|
|
5
|
-
export type ExpoIconComponent = React.ElementType<{
|
|
6
|
-
color?: string;
|
|
7
|
-
name?: string;
|
|
8
|
-
size?: number;
|
|
9
|
-
style?: StyleProp<TextStyle>;
|
|
10
|
-
testID?: string;
|
|
11
|
-
}>;
|
|
12
|
-
|
|
13
|
-
const EXPO_ICON_PROVIDER_ALIASES = {
|
|
14
|
-
'material-community': 'MaterialCommunityIcons',
|
|
15
|
-
} as const;
|
|
16
|
-
|
|
17
|
-
export function resolveExpoIconComponent(provider: string): ExpoIconComponent {
|
|
18
|
-
const normalizedProvider = resolveExpoIconProviderName(provider);
|
|
19
|
-
const candidate = (ExpoIcons as Record<string, unknown>)[normalizedProvider];
|
|
20
|
-
if (typeof candidate === 'function') {
|
|
21
|
-
return candidate as ExpoIconComponent;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return ExpoIcons.Ionicons as ExpoIconComponent;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolveExpoIconProviderName(provider: string): string {
|
|
28
|
-
const normalizedProvider = provider.trim().toLowerCase();
|
|
29
|
-
|
|
30
|
-
return normalizedProvider in EXPO_ICON_PROVIDER_ALIASES
|
|
31
|
-
? EXPO_ICON_PROVIDER_ALIASES[normalizedProvider as keyof typeof EXPO_ICON_PROVIDER_ALIASES]
|
|
32
|
-
: provider;
|
|
33
|
-
}
|