react-native-terra-ui 0.1.1 → 0.2.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 +96 -0
- package/lib/module/components/avatar/Avatar.js +118 -35
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/box/Box.js +15 -2
- package/lib/module/components/box/Box.js.map +1 -1
- package/lib/module/components/box/utils.js +4 -1
- package/lib/module/components/box/utils.js.map +1 -1
- package/lib/module/components/button/Button.js +7 -14
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/chip/Chip.js +372 -0
- package/lib/module/components/chip/Chip.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -0
- package/lib/module/components/chip/index.js.map +1 -0
- package/lib/module/components/chip/types.js +2 -0
- package/lib/module/components/chip/types.js.map +1 -0
- package/lib/module/components/chip/utils.js +36 -0
- package/lib/module/components/chip/utils.js.map +1 -0
- package/lib/module/components/empty-state/EmptyState.js +200 -0
- package/lib/module/components/empty-state/EmptyState.js.map +1 -0
- package/lib/module/components/empty-state/index.js +4 -0
- package/lib/module/components/empty-state/index.js.map +1 -0
- package/lib/module/components/empty-state/types.js +2 -0
- package/lib/module/components/empty-state/types.js.map +1 -0
- package/lib/module/components/header/header-buttons.js +31 -36
- package/lib/module/components/header/header-buttons.js.map +1 -1
- package/lib/module/components/header/index.js +1 -1
- package/lib/module/components/header/index.js.map +1 -1
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +38 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -0
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +69 -99
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +32 -63
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/icon/Icon.js +20 -8
- package/lib/module/components/icon/Icon.js.map +1 -1
- package/lib/module/components/image/Image.js +54 -0
- package/lib/module/components/image/Image.js.map +1 -0
- package/lib/module/components/image/index.js +4 -0
- package/lib/module/components/image/index.js.map +1 -0
- package/lib/module/components/image/types.js +4 -0
- package/lib/module/components/image/types.js.map +1 -0
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/portal/Portal.js +42 -0
- package/lib/module/components/portal/Portal.js.map +1 -0
- package/lib/module/components/portal/PortalContext.js +1 -16
- package/lib/module/components/portal/PortalContext.js.map +1 -1
- package/lib/module/components/portal/PortalHost.js +16 -14
- package/lib/module/components/portal/PortalHost.js.map +1 -1
- package/lib/module/components/portal/PortalProvider.js +51 -19
- package/lib/module/components/portal/PortalProvider.js.map +1 -1
- package/lib/module/components/portal/index.js +1 -1
- package/lib/module/components/portal/index.js.map +1 -1
- package/lib/module/components/screen/Screen.js +25 -28
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +7 -5
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenFlashList.js +144 -0
- package/lib/module/components/screen/ScreenFlashList.js.map +1 -0
- package/lib/module/components/screen/ScreenFlatList.js +88 -27
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +101 -34
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/index.js +1 -0
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/screen/parts/BottomSafeArea.js +62 -0
- package/lib/module/components/screen/parts/BottomSafeArea.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js +50 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -0
- package/lib/module/components/screen/types.js +4 -0
- package/lib/module/components/screen/types.js.map +1 -0
- package/lib/module/components/screen/utils.js +12 -9
- package/lib/module/components/screen/utils.js.map +1 -1
- package/lib/module/components/surface/Surface.js +16 -9
- package/lib/module/components/surface/Surface.js.map +1 -1
- package/lib/module/components/text/Text.js +23 -5
- package/lib/module/components/text/Text.js.map +1 -1
- package/lib/module/components/toast/Toast.js +8 -8
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toolbar/Toolbar.js +268 -0
- package/lib/module/components/toolbar/Toolbar.js.map +1 -0
- package/lib/module/components/toolbar/index.js +4 -0
- package/lib/module/components/toolbar/index.js.map +1 -0
- package/lib/module/context/ThemeProvider.js +5 -2
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/{components/icon/utils.js → theme/defaults/icons.js} +1 -1
- package/lib/module/theme/defaults/icons.js.map +1 -0
- package/lib/module/theme/defaults/image.js +30 -0
- package/lib/module/theme/defaults/image.js.map +1 -0
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/{configure.js → registry.js} +21 -28
- package/lib/module/theme/registry.js.map +1 -0
- package/lib/module/theme/runtime.js +1 -1
- package/lib/module/theme/theme.js +91 -10
- package/lib/module/theme/theme.js.map +1 -1
- package/lib/module/theme/tokens/dark.js +59 -68
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +49 -67
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +241 -20
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/module/utils/accent-utils.js +4 -4
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/types.d.ts +4 -4
- package/lib/typescript/src/components/avatar/types.d.ts.map +1 -1
- package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
- package/lib/typescript/src/components/box/utils.d.ts +6 -1
- package/lib/typescript/src/components/box/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/button/Button.d.ts +1 -1
- package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/chip/Chip.d.ts +24 -0
- package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts +4 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/types.d.ts +8 -0
- package/lib/typescript/src/components/chip/types.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/utils.d.ts +8 -0
- package/lib/typescript/src/components/chip/utils.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts +45 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/index.d.ts +4 -0
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/types.d.ts +6 -0
- package/lib/typescript/src/components/empty-state/types.d.ts.map +1 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts +27 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts.map +1 -1
- package/lib/typescript/src/components/header/index.d.ts +1 -1
- package/lib/typescript/src/components/header/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +7 -0
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +18 -7
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/components/image/Image.d.ts +16 -0
- package/lib/typescript/src/components/image/Image.d.ts.map +1 -0
- package/lib/typescript/src/components/image/index.d.ts +3 -0
- package/lib/typescript/src/components/image/index.d.ts.map +1 -0
- package/lib/typescript/src/components/image/types.d.ts +7 -0
- package/lib/typescript/src/components/image/types.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/Portal.d.ts +26 -0
- package/lib/typescript/src/components/portal/Portal.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/PortalContext.d.ts +11 -14
- package/lib/typescript/src/components/portal/PortalContext.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalHost.d.ts +9 -4
- package/lib/typescript/src/components/portal/PortalHost.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalProvider.d.ts +3 -3
- package/lib/typescript/src/components/portal/PortalProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/index.d.ts +4 -2
- package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/Screen.d.ts +18 -13
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +12 -9
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +43 -0
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +17 -7
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +51 -16
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/index.d.ts +3 -0
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +30 -0
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +22 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/types.d.ts +42 -0
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/utils.d.ts +9 -9
- package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts +7 -0
- package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts +115 -0
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/toolbar/index.d.ts +3 -0
- package/lib/typescript/src/components/toolbar/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/{components/icon/utils.d.ts → theme/defaults/icons.d.ts} +2 -2
- package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -0
- package/lib/typescript/src/theme/defaults/image.d.ts +4 -0
- package/lib/typescript/src/theme/defaults/image.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +1 -1
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/{configure.d.ts → registry.d.ts} +1 -1
- package/lib/typescript/src/theme/registry.d.ts.map +1 -0
- package/lib/typescript/src/theme/theme.d.ts +21 -1
- package/lib/typescript/src/theme/theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/dark.d.ts +41 -64
- package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/light.d.ts +44 -65
- package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +228 -17
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +65 -35
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +24 -12
- package/skills/terra-ui/SKILL.md +193 -0
- package/skills/terra-ui/references/recipes.md +198 -0
- package/skills/terra-ui/references/troubleshooting.md +112 -0
- package/src/components/avatar/Avatar.tsx +128 -38
- package/src/components/avatar/types.ts +5 -5
- package/src/components/box/Box.tsx +13 -1
- package/src/components/box/utils.ts +10 -0
- package/src/components/button/Button.tsx +7 -15
- package/src/components/chip/Chip.tsx +444 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/chip/types.ts +15 -0
- package/src/components/chip/utils.ts +34 -0
- package/src/components/empty-state/EmptyState.tsx +209 -0
- package/src/components/empty-state/index.ts +3 -0
- package/src/components/empty-state/types.ts +5 -0
- package/src/components/header/header-buttons.tsx +33 -45
- package/src/components/header/index.ts +2 -0
- package/src/components/header/parts/HeaderGradientOverlay.tsx +29 -0
- package/src/components/header/variants/LargeTitleHeader/index.tsx +68 -102
- package/src/components/header/variants/TitleHeader/index.tsx +47 -56
- package/src/components/icon/Icon.tsx +20 -15
- package/src/components/image/Image.tsx +48 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/image/types.ts +7 -0
- package/src/components/index.ts +4 -0
- package/src/components/portal/Portal.tsx +55 -0
- package/src/components/portal/PortalContext.tsx +14 -22
- package/src/components/portal/PortalHost.tsx +17 -10
- package/src/components/portal/PortalProvider.tsx +58 -21
- package/src/components/portal/index.ts +4 -2
- package/src/components/screen/Screen.tsx +45 -59
- package/src/components/screen/ScreenContext.tsx +19 -12
- package/src/components/screen/ScreenFlashList.tsx +196 -0
- package/src/components/screen/ScreenFlatList.tsx +100 -39
- package/src/components/screen/ScreenScrollView.tsx +114 -53
- package/src/components/screen/index.ts +3 -0
- package/src/components/screen/parts/BottomSafeArea.tsx +60 -0
- package/src/components/screen/parts/ScreenPlaceholder.tsx +55 -0
- package/src/components/screen/types.ts +43 -0
- package/src/components/screen/utils.ts +29 -15
- package/src/components/surface/Surface.tsx +17 -20
- package/src/components/text/Text.tsx +23 -5
- package/src/components/toast/Toast.tsx +8 -8
- package/src/components/toolbar/Toolbar.tsx +353 -0
- package/src/components/toolbar/index.ts +6 -0
- package/src/context/ThemeProvider.tsx +5 -4
- package/src/{components/icon/utils.tsx → theme/defaults/icons.tsx} +1 -1
- package/src/theme/defaults/image.tsx +34 -0
- package/src/theme/index.ts +1 -1
- package/src/theme/{configure.ts → registry.ts} +19 -34
- package/src/theme/runtime.ts +1 -1
- package/src/theme/theme.ts +127 -13
- package/src/theme/tokens/dark.ts +60 -69
- package/src/theme/tokens/light.ts +50 -68
- package/src/theme/tokens/primitives.ts +248 -20
- package/src/theme/types.ts +67 -35
- package/src/utils/accent-utils.ts +4 -4
- package/lib/module/components/icon/utils.js.map +0 -1
- package/lib/module/theme/configure.js.map +0 -1
- package/lib/typescript/src/components/icon/utils.d.ts.map +0 -1
- package/lib/typescript/src/theme/configure.d.ts.map +0 -1
|
@@ -2,9 +2,9 @@ import type { ComponentRef } from 'react';
|
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { View, type ViewProps } from 'react-native';
|
|
4
4
|
|
|
5
|
-
import { useUnistyles } from 'react-native-unistyles';
|
|
5
|
+
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
6
6
|
|
|
7
|
-
import { getIcon } from '#theme/
|
|
7
|
+
import { getIcon } from '#theme/registry';
|
|
8
8
|
import type { ColorToken, TerraIconName } from '#theme/types';
|
|
9
9
|
import { resolveThemeColor } from '#utils/resolve-theme-color';
|
|
10
10
|
|
|
@@ -50,19 +50,7 @@ export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(
|
|
|
50
50
|
'';
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
|
-
<View
|
|
54
|
-
ref={ref}
|
|
55
|
-
style={[
|
|
56
|
-
{
|
|
57
|
-
width: size,
|
|
58
|
-
height: size,
|
|
59
|
-
alignItems: 'center',
|
|
60
|
-
justifyContent: 'center',
|
|
61
|
-
},
|
|
62
|
-
style,
|
|
63
|
-
]}
|
|
64
|
-
{...rest}
|
|
65
|
-
>
|
|
53
|
+
<View ref={ref} style={[styles.container(size), style]} {...rest}>
|
|
66
54
|
<IconComponent
|
|
67
55
|
color={resolvedColor}
|
|
68
56
|
size={size}
|
|
@@ -72,3 +60,20 @@ export const Icon = forwardRef<ComponentRef<typeof View>, IconProps>(
|
|
|
72
60
|
);
|
|
73
61
|
}
|
|
74
62
|
);
|
|
63
|
+
|
|
64
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
65
|
+
//
|
|
66
|
+
// This stylesheet is also what makes the Unistyles Babel plugin swap the `View`
|
|
67
|
+
// imported above for its Unistyles-aware counterpart, which registers the
|
|
68
|
+
// ShadowNode natively. Without it, a Unistyles style passed in through `style`
|
|
69
|
+
// would freeze at whichever theme was active when it was created — `Icon` does
|
|
70
|
+
// not re-render on theme change unless something it reads from the theme
|
|
71
|
+
// happens to change.
|
|
72
|
+
const styles = StyleSheet.create(() => ({
|
|
73
|
+
container: (size: number) => ({
|
|
74
|
+
width: size,
|
|
75
|
+
height: size,
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
}),
|
|
79
|
+
}));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
3
|
+
import { getImageComponent } from '#theme/registry';
|
|
4
|
+
import type { RadiusKey } from '#theme/types';
|
|
5
|
+
import type { ImageProps } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Renders the image implementation configured via
|
|
9
|
+
* `configureTerraUI({ image })` (defaults to React Native's `Image`).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Image
|
|
14
|
+
* source={{ uri: 'https://example.com/photo.jpg' }}
|
|
15
|
+
* contentFit="cover"
|
|
16
|
+
* style={{ width: 40, height: 40 }}
|
|
17
|
+
* />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function Image({
|
|
21
|
+
contentFit = 'cover',
|
|
22
|
+
width,
|
|
23
|
+
height,
|
|
24
|
+
radius = 'none',
|
|
25
|
+
style,
|
|
26
|
+
...rest
|
|
27
|
+
}: ImageProps) {
|
|
28
|
+
const ImageComponent = getImageComponent();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View style={[styles.container(width, height, radius), style]}>
|
|
32
|
+
<ImageComponent contentFit={contentFit} style={styles.image} {...rest} />
|
|
33
|
+
</View>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const styles = StyleSheet.create((_theme) => ({
|
|
38
|
+
container: (w: number | undefined, h: number | undefined, r: RadiusKey) => ({
|
|
39
|
+
...(w ? { width: w } : {}),
|
|
40
|
+
...(h ? { height: h } : {}),
|
|
41
|
+
borderRadius: _theme.radius[r],
|
|
42
|
+
overflow: 'hidden',
|
|
43
|
+
}),
|
|
44
|
+
image: {
|
|
45
|
+
width: '100%',
|
|
46
|
+
height: '100%',
|
|
47
|
+
},
|
|
48
|
+
}));
|
package/src/components/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export * from './avatar';
|
|
2
2
|
export * from './box';
|
|
3
3
|
export * from './button';
|
|
4
|
+
export * from './chip';
|
|
5
|
+
export * from './empty-state';
|
|
4
6
|
export * from './header';
|
|
5
7
|
export * from './icon';
|
|
8
|
+
export * from './image';
|
|
6
9
|
export * from './page-indicator';
|
|
7
10
|
export * from './portal';
|
|
8
11
|
export * from './screen';
|
|
@@ -11,3 +14,4 @@ export * from './spinner';
|
|
|
11
14
|
export * from './surface';
|
|
12
15
|
export * from './text';
|
|
13
16
|
export * from './toast';
|
|
17
|
+
export * from './toolbar';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type FC, type ReactNode, useContext, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { PortalContext } from './PortalContext';
|
|
4
|
+
import { PortalHost, type PortalHostProps } from './PortalHost';
|
|
5
|
+
import { PortalProvider, type PortalProviderProps } from './PortalProvider';
|
|
6
|
+
|
|
7
|
+
let nextPortalKey = 1;
|
|
8
|
+
|
|
9
|
+
export interface PortalProps {
|
|
10
|
+
/** Name of the `PortalHost` to teleport `children` into. */
|
|
11
|
+
hostName: string;
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PortalComponent: FC<PortalProps> = ({ hostName, children }) => {
|
|
16
|
+
const context = useContext(PortalContext);
|
|
17
|
+
const upsertPortal = context?.upsertPortal;
|
|
18
|
+
const removePortal = context?.removePortal;
|
|
19
|
+
const portalKeyRef = useRef<number>(nextPortalKey++);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!upsertPortal || !removePortal) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
upsertPortal(hostName, portalKeyRef.current, children ?? null);
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
removePortal(hostName, portalKeyRef.current);
|
|
30
|
+
};
|
|
31
|
+
}, [children, hostName, removePortal, upsertPortal]);
|
|
32
|
+
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type PortalWithCompatApi = FC<PortalProps> & {
|
|
37
|
+
Provider: FC<PortalProviderProps>;
|
|
38
|
+
Host: FC<PortalHostProps>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Declaratively teleports `children` into the `PortalHost` named `hostName`,
|
|
43
|
+
* for as long as this component stays mounted.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <Portal hostName="screen-header">
|
|
48
|
+
* <LargeTitle />
|
|
49
|
+
* </Portal>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export const Portal = Object.assign(PortalComponent, {
|
|
53
|
+
Provider: PortalProvider,
|
|
54
|
+
Host: PortalHost,
|
|
55
|
+
}) as PortalWithCompatApi;
|
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import { createContext, type ReactNode
|
|
1
|
+
import { createContext, type ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** Remove the content previously registered under `id`. */
|
|
7
|
-
unregisterContent: (id: string) => void;
|
|
8
|
-
/** Currently registered content, keyed by id, rendered by `PortalHost`. */
|
|
9
|
-
contents: Map<string, ReactNode>;
|
|
3
|
+
export interface PortalEntry {
|
|
4
|
+
key: number;
|
|
5
|
+
node: ReactNode;
|
|
10
6
|
}
|
|
11
7
|
|
|
12
|
-
export
|
|
8
|
+
export type PortalStore = Record<string, PortalEntry[]>;
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export function usePortal(): PortalContextValue {
|
|
22
|
-
const context = useContext(PortalContext);
|
|
23
|
-
if (!context) {
|
|
24
|
-
throw new Error('usePortal must be used within a PortalProvider');
|
|
25
|
-
}
|
|
26
|
-
return context;
|
|
10
|
+
export interface PortalContextValue {
|
|
11
|
+
/** Portal entries currently registered, keyed by host name. */
|
|
12
|
+
portals: PortalStore;
|
|
13
|
+
/** Register (or replace) the entry `key` under `hostName`. */
|
|
14
|
+
upsertPortal: (hostName: string, key: number, node: ReactNode) => void;
|
|
15
|
+
/** Remove the entry `key` previously registered under `hostName`. */
|
|
16
|
+
removePortal: (hostName: string, key: number) => void;
|
|
27
17
|
}
|
|
18
|
+
|
|
19
|
+
export const PortalContext = createContext<PortalContextValue | null>(null);
|
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment, useContext } from 'react';
|
|
2
|
+
import { View, type ViewProps } from 'react-native';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { PortalContext } from './PortalContext';
|
|
5
|
+
|
|
6
|
+
export type PortalHostProps = Omit<ViewProps, 'children'> & {
|
|
7
|
+
/** Renders every `Portal` registered under this host name, in insertion order. */
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
|
-
* Renders every
|
|
7
|
-
* {@link PortalProvider}
|
|
8
|
-
*
|
|
12
|
+
* Renders every entry currently registered under `name` on the nearest
|
|
13
|
+
* {@link PortalProvider}. Place it where teleported content should appear
|
|
14
|
+
* (e.g. at the top of a `Screen.ScrollView`).
|
|
9
15
|
*/
|
|
10
|
-
export function PortalHost() {
|
|
11
|
-
const
|
|
16
|
+
export function PortalHost({ name, ...viewProps }: PortalHostProps) {
|
|
17
|
+
const context = useContext(PortalContext);
|
|
18
|
+
const hostPortals = context?.portals[name] ?? [];
|
|
12
19
|
|
|
13
20
|
return (
|
|
14
|
-
<View
|
|
15
|
-
{
|
|
16
|
-
<
|
|
21
|
+
<View {...viewProps}>
|
|
22
|
+
{hostPortals.map((entry) => (
|
|
23
|
+
<Fragment key={entry.key}>{entry.node}</Fragment>
|
|
17
24
|
))}
|
|
18
25
|
</View>
|
|
19
26
|
);
|
|
@@ -1,41 +1,78 @@
|
|
|
1
1
|
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PortalContext,
|
|
5
|
+
type PortalContextValue,
|
|
6
|
+
type PortalStore,
|
|
7
|
+
} from './PortalContext';
|
|
4
8
|
|
|
5
9
|
export interface PortalProviderProps {
|
|
6
10
|
children: ReactNode;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
|
-
* Holds a registry of teleported content
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* Holds a registry of teleported content, keyed by host name. Descendant
|
|
15
|
+
* `Portal` components register/unregister entries; a `PortalHost` placed
|
|
16
|
+
* anywhere below renders the entries for its `name`.
|
|
13
17
|
*/
|
|
14
18
|
export function PortalProvider({ children }: PortalProviderProps) {
|
|
15
|
-
const [
|
|
16
|
-
|
|
19
|
+
const [portals, setPortals] = useState<PortalStore>({});
|
|
20
|
+
|
|
21
|
+
const upsertPortal = useCallback(
|
|
22
|
+
(hostName: string, key: number, node: ReactNode) => {
|
|
23
|
+
setPortals((prev) => {
|
|
24
|
+
const hostPortals = prev[hostName] ?? [];
|
|
25
|
+
const existingIndex = hostPortals.findIndex(
|
|
26
|
+
(entry) => entry.key === key
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (existingIndex === -1) {
|
|
30
|
+
return {
|
|
31
|
+
...prev,
|
|
32
|
+
[hostName]: [...hostPortals, { key, node }],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (hostPortals[existingIndex]?.node === node) {
|
|
37
|
+
return prev;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const nextHostPortals = [...hostPortals];
|
|
41
|
+
nextHostPortals[existingIndex] = { key, node };
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
...prev,
|
|
45
|
+
[hostName]: nextHostPortals,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
[]
|
|
17
50
|
);
|
|
18
51
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
52
|
+
const removePortal = useCallback((hostName: string, key: number) => {
|
|
53
|
+
setPortals((prev) => {
|
|
54
|
+
const hostPortals = prev[hostName] ?? [];
|
|
55
|
+
const nextHostPortals = hostPortals.filter((entry) => entry.key !== key);
|
|
56
|
+
|
|
57
|
+
if (nextHostPortals.length === hostPortals.length) {
|
|
58
|
+
return prev;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (nextHostPortals.length === 0) {
|
|
62
|
+
const { [hostName]: _removed, ...rest } = prev;
|
|
63
|
+
return rest;
|
|
64
|
+
}
|
|
26
65
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
next.delete(id);
|
|
32
|
-
return next;
|
|
66
|
+
return {
|
|
67
|
+
...prev,
|
|
68
|
+
[hostName]: nextHostPortals,
|
|
69
|
+
};
|
|
33
70
|
});
|
|
34
71
|
}, []);
|
|
35
72
|
|
|
36
73
|
const value = useMemo<PortalContextValue>(
|
|
37
|
-
() => ({
|
|
38
|
-
[
|
|
74
|
+
() => ({ portals, upsertPortal, removePortal }),
|
|
75
|
+
[portals, removePortal, upsertPortal]
|
|
39
76
|
);
|
|
40
77
|
|
|
41
78
|
return (
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export {
|
|
1
|
+
export type { PortalProps } from './Portal';
|
|
2
|
+
export { Portal } from './Portal';
|
|
3
|
+
export type { PortalContextValue, PortalEntry } from './PortalContext';
|
|
4
|
+
export type { PortalHostProps } from './PortalHost';
|
|
3
5
|
export { PortalHost } from './PortalHost';
|
|
4
6
|
export type { PortalProviderProps } from './PortalProvider';
|
|
5
7
|
export { PortalProvider } from './PortalProvider';
|
|
@@ -10,20 +10,17 @@ import {
|
|
|
10
10
|
type ReactNode,
|
|
11
11
|
useMemo,
|
|
12
12
|
} from 'react';
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type SafeAreaViewProps,
|
|
17
|
-
} from 'react-native-safe-area-context';
|
|
18
|
-
import { useUnistyles } from 'react-native-unistyles';
|
|
19
|
-
|
|
13
|
+
import { View } from 'react-native';
|
|
14
|
+
import type { ViewProps } from 'react-native-svg/lib/typescript/fabric/utils';
|
|
15
|
+
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
20
16
|
import type { ColorToken } from '#theme/types';
|
|
21
17
|
import { resolveThemeColor } from '#utils/resolve-theme-color';
|
|
22
|
-
|
|
23
18
|
import { PortalProvider } from '../portal';
|
|
24
19
|
import { ScreenScrollProvider } from './ScreenContext';
|
|
20
|
+
import { ScreenFlashList } from './ScreenFlashList';
|
|
25
21
|
import { ScreenFlatList } from './ScreenFlatList';
|
|
26
22
|
import { ScreenScrollView } from './ScreenScrollView';
|
|
23
|
+
import type { ScreenMargins } from './types';
|
|
27
24
|
|
|
28
25
|
export type ScreenHeaderProps<T extends ElementType = typeof Fragment> =
|
|
29
26
|
PropsWithChildren<
|
|
@@ -57,43 +54,48 @@ function ScreenHeader<T extends ElementType = typeof Fragment>({
|
|
|
57
54
|
const Component = as ?? Fragment;
|
|
58
55
|
return <Component {...rest}>{children}</Component>;
|
|
59
56
|
}
|
|
57
|
+
ScreenHeader.displayName = 'Screen.Header';
|
|
60
58
|
|
|
61
59
|
/** True if `children` directly contains a `Screen.Header`. */
|
|
62
60
|
function hasScreenHeader(children: ReactNode): boolean {
|
|
63
61
|
let found = false;
|
|
64
62
|
Children.forEach(children, (child) => {
|
|
65
63
|
if (found) return;
|
|
66
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
isValidElement(child) &&
|
|
66
|
+
typeof child.type !== 'string' &&
|
|
67
|
+
(child.type as { displayName?: string }).displayName === 'Screen.Header'
|
|
68
|
+
)
|
|
69
|
+
found = true;
|
|
67
70
|
});
|
|
68
71
|
return found;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
export interface ScreenProps extends
|
|
74
|
+
export interface ScreenProps extends ViewProps {
|
|
72
75
|
children: ReactNode;
|
|
73
76
|
/** Background color token. Defaults to the `background` token. */
|
|
74
77
|
bg?: ColorToken;
|
|
75
|
-
/**
|
|
76
|
-
* Safe-area edges to inset. Defaults are header-aware: `[]` when a
|
|
77
|
-
* `Screen.Header` is present (the header owns the top inset), otherwise
|
|
78
|
-
* `['top', 'bottom']`. An explicit value is respected, except `top` is
|
|
79
|
-
* dropped when a header is present.
|
|
80
|
-
*/
|
|
81
|
-
edges?: Edge[];
|
|
82
78
|
/** Hosted inside a tab navigator? Exposed via `useScreen()`. Default false. */
|
|
83
79
|
inTabView?: boolean;
|
|
84
80
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
81
|
+
* Which axes of `layout.screen.margin` the scroll containers pad their
|
|
82
|
+
* content with. Default `'all'`. Overridable per scroll container.
|
|
83
|
+
*/
|
|
84
|
+
margins?: ScreenMargins;
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated Use `margins` — `false` maps to `margins="none"`. Ignored when
|
|
87
|
+
* `margins` is set.
|
|
87
88
|
*/
|
|
88
|
-
|
|
89
|
+
hasMargins?: boolean;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
type ScreenComponent = ReturnType<
|
|
92
|
-
typeof forwardRef<ComponentRef<typeof
|
|
93
|
+
typeof forwardRef<ComponentRef<typeof View>, ScreenProps>
|
|
93
94
|
> & {
|
|
94
95
|
Header: typeof ScreenHeader;
|
|
95
96
|
ScrollView: typeof ScreenScrollView;
|
|
96
97
|
FlatList: typeof ScreenFlatList;
|
|
98
|
+
FlashList: typeof ScreenFlashList;
|
|
97
99
|
};
|
|
98
100
|
|
|
99
101
|
/**
|
|
@@ -109,49 +111,26 @@ type ScreenComponent = ReturnType<
|
|
|
109
111
|
* </Screen>
|
|
110
112
|
* ```
|
|
111
113
|
*/
|
|
112
|
-
const ScreenBase = forwardRef<ComponentRef<typeof
|
|
114
|
+
const ScreenBase = forwardRef<ComponentRef<typeof View>, ScreenProps>(
|
|
113
115
|
function Screen(
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
bg = 'background',
|
|
117
|
-
edges,
|
|
118
|
-
inTabView = false,
|
|
119
|
-
margins = true,
|
|
120
|
-
style,
|
|
121
|
-
...rest
|
|
122
|
-
},
|
|
123
|
-
ref
|
|
116
|
+
{ children, bg = 'background', margins, hasMargins, style, ...rest },
|
|
117
|
+
_ref
|
|
124
118
|
) {
|
|
125
|
-
const { theme } = useUnistyles();
|
|
126
|
-
const backgroundColor =
|
|
127
|
-
resolveThemeColor(bg, theme) ?? theme.color.background;
|
|
128
|
-
|
|
129
119
|
const hasHeader = useMemo(() => hasScreenHeader(children), [children]);
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
return hasHeader ? edges.filter((edge) => edge !== 'top') : edges;
|
|
134
|
-
}
|
|
135
|
-
return hasHeader ? [] : ['top', 'bottom'];
|
|
136
|
-
}, [edges, hasHeader]);
|
|
120
|
+
const resolvedMargins = margins ?? (hasMargins === false ? 'none' : 'all');
|
|
121
|
+
const { theme } = useUnistyles();
|
|
122
|
+
const resolvedBg = resolveThemeColor(bg, theme) ?? theme.color.background;
|
|
137
123
|
|
|
138
124
|
return (
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
style={[{ flex: 1, backgroundColor }, style]}
|
|
149
|
-
{...rest}
|
|
150
|
-
>
|
|
151
|
-
{children}
|
|
152
|
-
</SafeAreaView>
|
|
153
|
-
</PortalProvider>
|
|
154
|
-
</ScreenScrollProvider>
|
|
125
|
+
<View style={[styles.container, style]} {...rest}>
|
|
126
|
+
<ScreenScrollProvider
|
|
127
|
+
background={resolvedBg}
|
|
128
|
+
margins={resolvedMargins}
|
|
129
|
+
hasHeader={hasHeader}
|
|
130
|
+
>
|
|
131
|
+
<PortalProvider>{children}</PortalProvider>
|
|
132
|
+
</ScreenScrollProvider>
|
|
133
|
+
</View>
|
|
155
134
|
);
|
|
156
135
|
}
|
|
157
136
|
) as ScreenComponent;
|
|
@@ -159,5 +138,12 @@ const ScreenBase = forwardRef<ComponentRef<typeof SafeAreaView>, ScreenProps>(
|
|
|
159
138
|
ScreenBase.Header = ScreenHeader;
|
|
160
139
|
ScreenBase.ScrollView = ScreenScrollView;
|
|
161
140
|
ScreenBase.FlatList = ScreenFlatList;
|
|
141
|
+
ScreenBase.FlashList = ScreenFlashList;
|
|
162
142
|
|
|
163
143
|
export const Screen = ScreenBase;
|
|
144
|
+
|
|
145
|
+
const styles = StyleSheet.create((_theme, _runtime) => ({
|
|
146
|
+
container: {
|
|
147
|
+
flex: 1,
|
|
148
|
+
},
|
|
149
|
+
}));
|
|
@@ -16,14 +16,20 @@ import Animated, {
|
|
|
16
16
|
useSharedValue,
|
|
17
17
|
} from 'react-native-reanimated';
|
|
18
18
|
|
|
19
|
+
import type { ScreenMargins } from './types';
|
|
20
|
+
|
|
19
21
|
export type ScreenScrollRef = AnimatedRef<Animated.ScrollView>;
|
|
20
22
|
|
|
23
|
+
/** `PortalHost` name shared by `Screen.ScrollView` / `Screen.FlatList` and header variants that portal content into them. */
|
|
24
|
+
export const SCREEN_HEADER_PORTAL_HOST = 'screen-header';
|
|
25
|
+
|
|
21
26
|
export interface ScreenContextValue {
|
|
22
27
|
/**
|
|
23
|
-
*
|
|
24
|
-
* Overridable per `Screen.ScrollView` / `Screen.FlatList
|
|
28
|
+
* Which axes scroll containers pad with `layout.screen.margin`.
|
|
29
|
+
* Overridable per `Screen.ScrollView` / `Screen.FlatList` /
|
|
30
|
+
* `Screen.FlashList` via their own `margins` prop.
|
|
25
31
|
*/
|
|
26
|
-
margins:
|
|
32
|
+
margins: ScreenMargins;
|
|
27
33
|
/** Whether a `Screen.Header` is present. Used by scroll containers to add top inset. */
|
|
28
34
|
hasHeader: boolean;
|
|
29
35
|
/**
|
|
@@ -49,19 +55,20 @@ export interface ScreenContextValue {
|
|
|
49
55
|
scrollRef: ScreenScrollRef;
|
|
50
56
|
/** Scroll handler that mirrors offset into `scrollY`. */
|
|
51
57
|
scrollHandler: ScrollHandlerProcessed<Record<string, unknown>>;
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
|
|
59
|
+
background?: string;
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
export const ScreenContext = createContext<ScreenContextValue | null>(null);
|
|
57
63
|
|
|
58
64
|
export interface ScreenScrollProviderProps {
|
|
59
65
|
children: ReactNode;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
margins?: boolean;
|
|
66
|
+
/** Which axes scroll containers pad with `layout.screen.margin`. Default `'all'`. */
|
|
67
|
+
margins?: ScreenMargins;
|
|
63
68
|
/** Whether a `Screen.Header` child is present. Default false. */
|
|
64
69
|
hasHeader?: boolean;
|
|
70
|
+
|
|
71
|
+
background?: string;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
/**
|
|
@@ -70,9 +77,9 @@ export interface ScreenScrollProviderProps {
|
|
|
70
77
|
* scroll containers bind the ref via {@link useScreen}.
|
|
71
78
|
*/
|
|
72
79
|
export function ScreenScrollProvider({
|
|
80
|
+
background = 'transparent',
|
|
73
81
|
children,
|
|
74
|
-
|
|
75
|
-
margins = true,
|
|
82
|
+
margins = 'all',
|
|
76
83
|
hasHeader = false,
|
|
77
84
|
}: ScreenScrollProviderProps) {
|
|
78
85
|
const scrollY = useSharedValue(0);
|
|
@@ -97,6 +104,7 @@ export function ScreenScrollProvider({
|
|
|
97
104
|
|
|
98
105
|
const value = useMemo<ScreenContextValue>(
|
|
99
106
|
() => ({
|
|
107
|
+
background,
|
|
100
108
|
margins,
|
|
101
109
|
hasHeader,
|
|
102
110
|
scrollY,
|
|
@@ -105,9 +113,9 @@ export function ScreenScrollProvider({
|
|
|
105
113
|
setHeaderCollapseHeight,
|
|
106
114
|
scrollRef,
|
|
107
115
|
scrollHandler,
|
|
108
|
-
isInTabView,
|
|
109
116
|
}),
|
|
110
117
|
[
|
|
118
|
+
background,
|
|
111
119
|
margins,
|
|
112
120
|
hasHeader,
|
|
113
121
|
scrollY,
|
|
@@ -116,7 +124,6 @@ export function ScreenScrollProvider({
|
|
|
116
124
|
setHeaderCollapseHeight,
|
|
117
125
|
scrollRef,
|
|
118
126
|
scrollHandler,
|
|
119
|
-
isInTabView,
|
|
120
127
|
]
|
|
121
128
|
);
|
|
122
129
|
|