@opndev/react-native-events 0.0.10 → 0.0.11
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/Changes +32 -0
- package/lib/components/base-screen.jsx +56 -0
- package/lib/components/hero-screen-fixed.jsx +96 -0
- package/lib/components/hero-screen-header.jsx +41 -0
- package/lib/components/hero-screen-parallax.jsx +46 -0
- package/lib/components/hero-screen.jsx +60 -66
- package/lib/components/qr-code-form.jsx +39 -9
- package/lib/hooks/use-safe-area-top-inset.js +16 -0
- package/lib/index.js +1 -1
- package/lib/{screen-registery.js → screen-registry.js} +47 -0
- package/package.json +6 -5
- /package/lib/{hero-screen-registery.js → hero-screen-registry.js} +0 -0
package/Changes
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
Revision history for @opndev/opndev-react-native-events
|
|
2
2
|
|
|
3
|
+
0.0.11 2026-06-25 04:12:51Z
|
|
4
|
+
|
|
5
|
+
* registry not registery :/
|
|
6
|
+
* Add HeroScreen variants: Parallax, Fixed and Overlay. Overlay is the
|
|
7
|
+
default.
|
|
8
|
+
* Move consuming units to screen-registry, so consuming apps can just use:
|
|
9
|
+
|
|
10
|
+
import { createScreens } from
|
|
11
|
+
'@opndev/react-native-events/screen-registry';
|
|
12
|
+
import { ThemedText } from '@/components/themed-text';
|
|
13
|
+
import { styles } from '@/styles/tabs';
|
|
14
|
+
|
|
15
|
+
const CategoryText =
|
|
16
|
+
(props) => <ThemedText type="subtitle" {...props} />;
|
|
17
|
+
|
|
18
|
+
export const {
|
|
19
|
+
heroImage,
|
|
20
|
+
FoodMenuScreen,
|
|
21
|
+
FoodVendorScreen,
|
|
22
|
+
HeroScreen,
|
|
23
|
+
HeroScreenParallax,
|
|
24
|
+
HeroScreenFixed,
|
|
25
|
+
NewsItemScreen,
|
|
26
|
+
NewsListScreen,
|
|
27
|
+
QRCodeScreen,
|
|
28
|
+
Tile,
|
|
29
|
+
} = createScreens({
|
|
30
|
+
TextComponent: ThemedText,
|
|
31
|
+
CategoryTextComponent: CategoryText,
|
|
32
|
+
styles,
|
|
33
|
+
});
|
|
34
|
+
|
|
3
35
|
0.0.10 2026-06-21 17:41:27Z
|
|
4
36
|
|
|
5
37
|
* Rename @opndev/opndev-react-native-events to @opndev/react-native-events,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { View, ScrollView, StyleSheet } from 'react-native';
|
|
2
|
+
import { useSafeAreaTopInset } from '../hooks/use-safe-area-top-inset.js';
|
|
3
|
+
|
|
4
|
+
const defaultStyle = StyleSheet.create({
|
|
5
|
+
container: {
|
|
6
|
+
flex: 1,
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* BaseScreen
|
|
12
|
+
*
|
|
13
|
+
* Plain screen wrapper for content that doesn't have a hero header
|
|
14
|
+
* image (e.g. NewsListScreen, FoodMenuScreen). Encapsulates the
|
|
15
|
+
* same background / safe-area / scroll-container concerns as the
|
|
16
|
+
* HeroScreen variants, minus the header, so non-hero screens still
|
|
17
|
+
* get consistent top-inset behaviour rather than each handling it
|
|
18
|
+
* (or forgetting to handle it) separately.
|
|
19
|
+
*
|
|
20
|
+
* @param {object} props
|
|
21
|
+
* @param {React.ReactNode} props.children
|
|
22
|
+
* @param {string} [props.backgroundColor]
|
|
23
|
+
* @param {React.ComponentType} [props.ContentComponent] Defaults to ScrollView; pass a list component (FlatList/SectionList) if children render as list items
|
|
24
|
+
* @param {object} [props.containerStyle]
|
|
25
|
+
* @param {object} [props.contentStyle]
|
|
26
|
+
* @param {boolean} [props.useSafeArea] Pad content by the top safe-area inset. Defaults true.
|
|
27
|
+
*
|
|
28
|
+
* @returns {JSX.Element}
|
|
29
|
+
*/
|
|
30
|
+
export default function BaseScreen({
|
|
31
|
+
children,
|
|
32
|
+
backgroundColor,
|
|
33
|
+
ContentComponent = ScrollView,
|
|
34
|
+
containerStyle,
|
|
35
|
+
contentStyle,
|
|
36
|
+
useSafeArea = true,
|
|
37
|
+
}) {
|
|
38
|
+
const insetTop = useSafeAreaTopInset();
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View
|
|
42
|
+
style={[
|
|
43
|
+
defaultStyle.container,
|
|
44
|
+
backgroundColor ? { backgroundColor } : null,
|
|
45
|
+
containerStyle,
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
48
|
+
<ContentComponent
|
|
49
|
+
style={[contentStyle]}
|
|
50
|
+
contentContainerStyle={useSafeArea ? { paddingTop: insetTop } : null}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</ContentComponent>
|
|
54
|
+
</View>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { View, ScrollView, StyleSheet } from 'react-native';
|
|
2
|
+
import {
|
|
3
|
+
renderHeaderImage,
|
|
4
|
+
HeaderOverlay,
|
|
5
|
+
useHeroHeaderHeight
|
|
6
|
+
} from './hero-screen-header';
|
|
7
|
+
|
|
8
|
+
const defaultStyle = StyleSheet.create({
|
|
9
|
+
container: {
|
|
10
|
+
flex: 1,
|
|
11
|
+
},
|
|
12
|
+
headerWrap: {
|
|
13
|
+
width: '100%',
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
top: 0,
|
|
16
|
+
left: 0,
|
|
17
|
+
},
|
|
18
|
+
imageInner: {
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
left: 0,
|
|
21
|
+
width: '100%',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* HeroScreenFixed
|
|
27
|
+
*
|
|
28
|
+
* Header image stays pinned in place; only the content below it
|
|
29
|
+
* scrolls. The image never overlaps the status bar — a solid
|
|
30
|
+
* headerBackgroundColor strip (sized to the safe-area top inset,
|
|
31
|
+
* via useHeroHeaderHeight) sits above it.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} props
|
|
34
|
+
* @param {React.ReactNode} props.children
|
|
35
|
+
* @param {object} [props.headerImage]
|
|
36
|
+
* @param {React.ReactNode} [props.headerOverlay]
|
|
37
|
+
* @param {string} [props.backgroundColor]
|
|
38
|
+
* @param {string} [props.headerBackgroundColor]
|
|
39
|
+
* @param {number} [props.headerHeight] Visible image height (excludes the safe-area inset)
|
|
40
|
+
* @param {React.ComponentType} [props.ContentComponent]
|
|
41
|
+
* @param {object} [props.containerStyle]
|
|
42
|
+
* @param {object} [props.headerStyle]
|
|
43
|
+
* @param {object} [props.contentStyle]
|
|
44
|
+
*
|
|
45
|
+
* @returns {JSX.Element}
|
|
46
|
+
*/
|
|
47
|
+
export default function HeroScreenFixed({
|
|
48
|
+
children,
|
|
49
|
+
headerImage,
|
|
50
|
+
headerOverlay,
|
|
51
|
+
backgroundColor,
|
|
52
|
+
headerBackgroundColor,
|
|
53
|
+
headerHeight = 220,
|
|
54
|
+
ContentComponent = ScrollView,
|
|
55
|
+
containerStyle,
|
|
56
|
+
headerStyle,
|
|
57
|
+
contentStyle,
|
|
58
|
+
}) {
|
|
59
|
+
const { insetTop, totalHeaderHeight } = useHeroHeaderHeight(headerHeight);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View
|
|
63
|
+
style={[
|
|
64
|
+
defaultStyle.container,
|
|
65
|
+
backgroundColor ? { backgroundColor } : null,
|
|
66
|
+
containerStyle,
|
|
67
|
+
]}
|
|
68
|
+
>
|
|
69
|
+
<View
|
|
70
|
+
style={[
|
|
71
|
+
defaultStyle.headerWrap,
|
|
72
|
+
{ height: totalHeaderHeight },
|
|
73
|
+
headerBackgroundColor ? { backgroundColor: headerBackgroundColor } : null,
|
|
74
|
+
headerStyle,
|
|
75
|
+
]}
|
|
76
|
+
>
|
|
77
|
+
<View
|
|
78
|
+
style={[
|
|
79
|
+
defaultStyle.imageInner,
|
|
80
|
+
{ top: insetTop, height: headerHeight },
|
|
81
|
+
]}
|
|
82
|
+
>
|
|
83
|
+
{renderHeaderImage(headerImage)}
|
|
84
|
+
</View>
|
|
85
|
+
</View>
|
|
86
|
+
|
|
87
|
+
<ContentComponent
|
|
88
|
+
style={[contentStyle]}
|
|
89
|
+
contentContainerStyle={{ paddingTop: totalHeaderHeight }}
|
|
90
|
+
>
|
|
91
|
+
<HeaderOverlay headerOverlay={headerOverlay} />
|
|
92
|
+
{children}
|
|
93
|
+
</ContentComponent>
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { View, Image, StyleSheet } from 'react-native';
|
|
2
|
+
import { useSafeAreaTopInset } from '../hooks/use-safe-area-top-inset.js';
|
|
3
|
+
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
headerImage: { width: '100%', height: '100%' },
|
|
6
|
+
titleWrap: { paddingHorizontal: 8, paddingTop: 8, paddingBottom: 8 },
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export function renderHeaderImage(headerImage) {
|
|
10
|
+
if (!headerImage) return null;
|
|
11
|
+
const fit = headerImage.fit || 'cover';
|
|
12
|
+
const style = headerImage.style || styles.headerImage;
|
|
13
|
+
return <Image source={headerImage.source} style={style} resizeMode={fit} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function HeaderOverlay({ headerOverlay }) {
|
|
17
|
+
if (!headerOverlay) return null;
|
|
18
|
+
return <View style={styles.titleWrap}>{headerOverlay}</View>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* useHeroHeaderHeight
|
|
23
|
+
*
|
|
24
|
+
* Shared safe-area calculation for all HeroScreen variants, so the
|
|
25
|
+
* "space above the header" behaves identically across Parallax,
|
|
26
|
+
* Fixed, and Overlay rather than each computing it separately.
|
|
27
|
+
*
|
|
28
|
+
* @param {number} [headerHeight] Visible image height, as passed by the caller (excludes the inset)
|
|
29
|
+
* @returns {{ insetTop: number, headerHeight: number, totalHeaderHeight: number }}
|
|
30
|
+
*/
|
|
31
|
+
export function useHeroHeaderHeight(headerHeight = 220) {
|
|
32
|
+
const insetTop = useSafeAreaTopInset();
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
insetTop,
|
|
36
|
+
headerHeight,
|
|
37
|
+
totalHeaderHeight: insetTop + headerHeight,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { styles as heroImageStyles };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { View, StyleSheet } from 'react-native';
|
|
2
|
+
import ParallaxScrollView from './parallax-scroll-view';
|
|
3
|
+
import { renderHeaderImage, HeaderOverlay } from './hero-screen-header';
|
|
4
|
+
|
|
5
|
+
const defaultStyle = StyleSheet.create({
|
|
6
|
+
headerImageWrap: {
|
|
7
|
+
width: '100%',
|
|
8
|
+
height: 220,
|
|
9
|
+
bottom: 0,
|
|
10
|
+
left: 0,
|
|
11
|
+
position: 'absolute',
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default function HeroScreen({
|
|
16
|
+
children,
|
|
17
|
+
headerImage,
|
|
18
|
+
headerOverlay,
|
|
19
|
+
backgroundColor,
|
|
20
|
+
headerBackgroundColor,
|
|
21
|
+
headerHeight,
|
|
22
|
+
ContentComponent,
|
|
23
|
+
containerStyle,
|
|
24
|
+
headerStyle,
|
|
25
|
+
contentStyle,
|
|
26
|
+
}) {
|
|
27
|
+
return (
|
|
28
|
+
<ParallaxScrollView
|
|
29
|
+
backgroundColor={backgroundColor}
|
|
30
|
+
headerBackgroundColor={headerBackgroundColor}
|
|
31
|
+
headerHeight={headerHeight}
|
|
32
|
+
ContentComponent={ContentComponent}
|
|
33
|
+
containerStyle={containerStyle}
|
|
34
|
+
headerStyle={headerStyle}
|
|
35
|
+
contentStyle={contentStyle}
|
|
36
|
+
headerImage={
|
|
37
|
+
<View style={defaultStyle.headerImageWrap}>
|
|
38
|
+
{renderHeaderImage(headerImage)}
|
|
39
|
+
</View>
|
|
40
|
+
}
|
|
41
|
+
>
|
|
42
|
+
<HeaderOverlay headerOverlay={headerOverlay} />
|
|
43
|
+
{children}
|
|
44
|
+
</ParallaxScrollView>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -2,108 +2,102 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-OPNDEV-exceptions
|
|
4
4
|
|
|
5
|
-
import { View,
|
|
6
|
-
import
|
|
5
|
+
import { View, ScrollView, StyleSheet } from 'react-native';
|
|
6
|
+
import {
|
|
7
|
+
renderHeaderImage,
|
|
8
|
+
HeaderOverlay,
|
|
9
|
+
useHeroHeaderHeight
|
|
10
|
+
} from './hero-screen-header';
|
|
7
11
|
|
|
8
12
|
const defaultStyle = StyleSheet.create({
|
|
9
|
-
|
|
13
|
+
container: {
|
|
14
|
+
flex: 1,
|
|
15
|
+
},
|
|
16
|
+
headerWrap: {
|
|
10
17
|
width: '100%',
|
|
11
|
-
height: 220,
|
|
12
|
-
bottom: 0,
|
|
13
|
-
left: 0,
|
|
14
18
|
position: 'absolute',
|
|
19
|
+
top: 0,
|
|
20
|
+
left: 0,
|
|
21
|
+
zIndex: 1,
|
|
15
22
|
},
|
|
16
|
-
|
|
23
|
+
imageInner: {
|
|
24
|
+
position: 'absolute',
|
|
25
|
+
left: 0,
|
|
17
26
|
width: '100%',
|
|
18
|
-
height: '100%',
|
|
19
|
-
},
|
|
20
|
-
titleWrap: {
|
|
21
|
-
paddingHorizontal: 8,
|
|
22
|
-
paddingTop: 8,
|
|
23
|
-
paddingBottom: 8,
|
|
24
27
|
},
|
|
25
28
|
});
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
|
-
*
|
|
31
|
+
* HeroScreenOverlay
|
|
29
32
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* Header image stays pinned on top; content scrolls underneath it
|
|
34
|
+
* (visible content gets covered by the image as the user scrolls
|
|
35
|
+
* up). The image never overlaps the status bar — a solid
|
|
36
|
+
* headerBackgroundColor strip (sized to the safe-area top inset,
|
|
37
|
+
* via useHeroHeaderHeight) sits above it.
|
|
34
38
|
*
|
|
35
39
|
* @param {object} props
|
|
36
40
|
* @param {React.ReactNode} props.children
|
|
37
41
|
* @param {object} [props.headerImage]
|
|
38
|
-
* Header image config:
|
|
39
|
-
* - source: React Native image source
|
|
40
|
-
* - style: optional image style
|
|
41
|
-
* - fit: optional resize mode, defaults to 'cover'
|
|
42
42
|
* @param {React.ReactNode} [props.headerOverlay]
|
|
43
|
-
* Optional content rendered below the header image.
|
|
44
43
|
* @param {string} [props.backgroundColor]
|
|
45
44
|
* @param {string} [props.headerBackgroundColor]
|
|
46
|
-
* @param {number} [props.headerHeight]
|
|
47
|
-
* @param {React.ComponentType
|
|
48
|
-
* [props.ContentComponent]
|
|
45
|
+
* @param {number} [props.headerHeight] Visible image height (excludes the safe-area inset)
|
|
46
|
+
* @param {React.ComponentType} [props.ContentComponent]
|
|
49
47
|
* @param {object} [props.containerStyle]
|
|
50
48
|
* @param {object} [props.headerStyle]
|
|
51
49
|
* @param {object} [props.contentStyle]
|
|
52
50
|
*
|
|
53
51
|
* @returns {JSX.Element}
|
|
54
52
|
*/
|
|
55
|
-
export default function
|
|
53
|
+
export default function HeroScreenOverlay({
|
|
56
54
|
children,
|
|
57
55
|
headerImage,
|
|
58
56
|
headerOverlay,
|
|
59
|
-
|
|
60
57
|
backgroundColor,
|
|
61
58
|
headerBackgroundColor,
|
|
62
|
-
headerHeight,
|
|
63
|
-
|
|
64
|
-
ContentComponent,
|
|
59
|
+
headerHeight = 220,
|
|
60
|
+
ContentComponent = ScrollView,
|
|
65
61
|
containerStyle,
|
|
66
62
|
headerStyle,
|
|
67
63
|
contentStyle,
|
|
68
64
|
}) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (headerImage) {
|
|
72
|
-
const fit = headerImage.fit || 'cover';
|
|
73
|
-
const style = headerImage.style || defaultStyle.headerImage;
|
|
74
|
-
|
|
75
|
-
hi = (
|
|
76
|
-
<Image
|
|
77
|
-
source={headerImage.source}
|
|
78
|
-
style={style}
|
|
79
|
-
resizeMode={fit}
|
|
80
|
-
/>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
65
|
+
const { insetTop, totalHeaderHeight } = useHeroHeaderHeight(headerHeight);
|
|
83
66
|
|
|
84
67
|
return (
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
headerStyle={headerStyle}
|
|
92
|
-
contentStyle={contentStyle}
|
|
93
|
-
headerImage={
|
|
94
|
-
<View style={defaultStyle.headerImageWrap}>
|
|
95
|
-
{hi}
|
|
96
|
-
</View>
|
|
97
|
-
}
|
|
68
|
+
<View
|
|
69
|
+
style={[
|
|
70
|
+
defaultStyle.container,
|
|
71
|
+
backgroundColor ? { backgroundColor } : null,
|
|
72
|
+
containerStyle,
|
|
73
|
+
]}
|
|
98
74
|
>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
<ContentComponent
|
|
76
|
+
style={[contentStyle]}
|
|
77
|
+
contentContainerStyle={{ paddingTop: 0 }}
|
|
78
|
+
>
|
|
79
|
+
<View style={{ height: totalHeaderHeight }} />
|
|
80
|
+
<HeaderOverlay headerOverlay={headerOverlay} />
|
|
81
|
+
{children}
|
|
82
|
+
</ContentComponent>
|
|
104
83
|
|
|
105
|
-
|
|
106
|
-
|
|
84
|
+
<View
|
|
85
|
+
style={[
|
|
86
|
+
defaultStyle.headerWrap,
|
|
87
|
+
{ height: totalHeaderHeight },
|
|
88
|
+
headerBackgroundColor ? { backgroundColor: headerBackgroundColor } : null,
|
|
89
|
+
headerStyle,
|
|
90
|
+
]}
|
|
91
|
+
>
|
|
92
|
+
<View
|
|
93
|
+
style={[
|
|
94
|
+
defaultStyle.imageInner,
|
|
95
|
+
{ top: insetTop, height: headerHeight },
|
|
96
|
+
]}
|
|
97
|
+
>
|
|
98
|
+
{renderHeaderImage(headerImage)}
|
|
99
|
+
</View>
|
|
100
|
+
</View>
|
|
101
|
+
</View>
|
|
107
102
|
);
|
|
108
103
|
}
|
|
109
|
-
|
|
@@ -179,6 +179,7 @@ export default function QRCodeForm({
|
|
|
179
179
|
const responseError = getResponseError(response);
|
|
180
180
|
const visibleError = error || responseError;
|
|
181
181
|
const hasResult = Boolean(response && !responseError);
|
|
182
|
+
const showResponseErrorCard = Boolean(response && responseError);
|
|
182
183
|
const token = hasResult ? action.getToken(response) : undefined;
|
|
183
184
|
|
|
184
185
|
const renderArgs = {
|
|
@@ -297,7 +298,7 @@ export default function QRCodeForm({
|
|
|
297
298
|
* @returns {JSX.Element|null}
|
|
298
299
|
*/
|
|
299
300
|
function renderResult() {
|
|
300
|
-
if (!hasResult) {
|
|
301
|
+
if (!hasResult && !showResponseErrorCard) {
|
|
301
302
|
return null;
|
|
302
303
|
}
|
|
303
304
|
|
|
@@ -312,10 +313,10 @@ export default function QRCodeForm({
|
|
|
312
313
|
]}
|
|
313
314
|
>
|
|
314
315
|
{renderAboveQRCode ? renderAboveQRCode(renderArgs) : null}
|
|
315
|
-
{renderQRCode()}
|
|
316
|
-
{renderBelowQRCode ? renderBelowQRCode(renderArgs) : null}
|
|
317
|
-
{renderRefreshButton()}
|
|
318
|
-
{renderResetLink()}
|
|
316
|
+
{hasResult ? renderQRCode() : null}
|
|
317
|
+
{hasResult && renderBelowQRCode ? renderBelowQRCode(renderArgs) : null}
|
|
318
|
+
{hasResult ? renderRefreshButton() : null}
|
|
319
|
+
{hasResult ? renderResetLink() : renderResetButton()}
|
|
319
320
|
</View>
|
|
320
321
|
);
|
|
321
322
|
}
|
|
@@ -390,10 +391,41 @@ export default function QRCodeForm({
|
|
|
390
391
|
}
|
|
391
392
|
|
|
392
393
|
/**
|
|
394
|
+
* Button version of reset, used in place of the link when showing
|
|
395
|
+
* the error-state card (response.error), so it matches the
|
|
396
|
+
* success card's button-style affordance instead of a text link.
|
|
397
|
+
*
|
|
398
|
+
* @returns {JSX.Element}
|
|
399
|
+
*/
|
|
400
|
+
function renderResetButton() {
|
|
401
|
+
return (
|
|
402
|
+
<Pressable
|
|
403
|
+
style={[
|
|
404
|
+
defaultStyle.button,
|
|
405
|
+
defaultStyle.wideButton,
|
|
406
|
+
secondaryButtonBackgroundColor
|
|
407
|
+
? { backgroundColor: secondaryButtonBackgroundColor }
|
|
408
|
+
: null,
|
|
409
|
+
secondaryButtonStyle,
|
|
410
|
+
]}
|
|
411
|
+
onPress={reset}
|
|
412
|
+
>
|
|
413
|
+
<TextComponent style={secondaryButtonTextStyle}>
|
|
414
|
+
{resetLabel}
|
|
415
|
+
</TextComponent>
|
|
416
|
+
</Pressable>
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Only handles the network/throw-style error (from the catch block
|
|
422
|
+
* in fetchQr). Response-level errors (response.error) are now
|
|
423
|
+
* rendered inside renderResult() as a styled card instead.
|
|
424
|
+
*
|
|
393
425
|
* @returns {JSX.Element|null}
|
|
394
426
|
*/
|
|
395
427
|
function renderError() {
|
|
396
|
-
if (!
|
|
428
|
+
if (!error) {
|
|
397
429
|
return null;
|
|
398
430
|
}
|
|
399
431
|
|
|
@@ -412,10 +444,8 @@ export default function QRCodeForm({
|
|
|
412
444
|
errorTextStyle,
|
|
413
445
|
]}
|
|
414
446
|
>
|
|
415
|
-
{
|
|
447
|
+
{error}
|
|
416
448
|
</TextComponent>
|
|
417
|
-
|
|
418
|
-
{responseError ? renderResetLink() : null}
|
|
419
449
|
</View>
|
|
420
450
|
);
|
|
421
451
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useSafeAreaTopInset
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth for "how much space does the status bar /
|
|
7
|
+
* notch take up". Used by both HeroScreen variants (via
|
|
8
|
+
* useHeroHeaderHeight) and plain Screen, so every screen in the
|
|
9
|
+
* package agrees on the same number.
|
|
10
|
+
*
|
|
11
|
+
* @returns {number}
|
|
12
|
+
*/
|
|
13
|
+
export function useSafeAreaTopInset() {
|
|
14
|
+
const insets = useSafeAreaInsets();
|
|
15
|
+
return insets.top;
|
|
16
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
+
import BaseScreen from './components/base-screen';
|
|
7
8
|
import HeroScreen from './components/hero-screen';
|
|
9
|
+
import HeroScreenParallax from './components/hero-screen-parallax';
|
|
10
|
+
import HeroScreenFixed from './components/hero-screen-fixed';
|
|
8
11
|
import Tile from './components/tile';
|
|
9
12
|
|
|
10
13
|
import FoodMenuScreen from './screens/food-menu-screen';
|
|
@@ -13,15 +16,59 @@ import NewsItemScreen from './screens/news-item-screen';
|
|
|
13
16
|
import NewsListScreen from './screens/news-list-screen';
|
|
14
17
|
import QRCodeScreen from './screens/qr-code-screen';
|
|
15
18
|
|
|
19
|
+
/**
|
|
20
|
+
* createScreens
|
|
21
|
+
*
|
|
22
|
+
* @param {object} [options]
|
|
23
|
+
* @param {React.ComponentType} [options.TextComponent]
|
|
24
|
+
* @param {React.ComponentType} [options.CategoryTextComponent]
|
|
25
|
+
* @param {object} [options.styles]
|
|
26
|
+
* App-provided style object. Used as the default source for
|
|
27
|
+
* heroImage() when a caller doesn't pass an explicit style/fit.
|
|
28
|
+
* Expected (optional) keys: heroImage, heroImageFit.
|
|
29
|
+
*
|
|
30
|
+
* @returns {object} Named screens + helpers, ready to re-export.
|
|
31
|
+
*/
|
|
16
32
|
export function createScreens({
|
|
17
33
|
TextComponent,
|
|
18
34
|
CategoryTextComponent,
|
|
35
|
+
styles = {},
|
|
19
36
|
} = {}) {
|
|
37
|
+
/**
|
|
38
|
+
* heroImage
|
|
39
|
+
*
|
|
40
|
+
* @param {any} source
|
|
41
|
+
* @param {object} [options]
|
|
42
|
+
* @param {object} [options.style]
|
|
43
|
+
* @param {string} [options.fit] // 'cover', 'contain', etc
|
|
44
|
+
*/
|
|
45
|
+
function heroImage(source, options = {}) {
|
|
46
|
+
return {
|
|
47
|
+
source,
|
|
48
|
+
style: options.style || styles.heroImage,
|
|
49
|
+
fit: options.fit || styles.heroImageFit,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
20
53
|
return {
|
|
54
|
+
heroImage,
|
|
55
|
+
|
|
21
56
|
HeroScreen: (props) => (
|
|
22
57
|
<HeroScreen {...props} />
|
|
23
58
|
),
|
|
24
59
|
|
|
60
|
+
HeroScreenParallax: (props) => (
|
|
61
|
+
<HeroScreenParallax {...props} />
|
|
62
|
+
),
|
|
63
|
+
|
|
64
|
+
HeroScreenFixed: (props) => (
|
|
65
|
+
<HeroScreenFixed {...props} />
|
|
66
|
+
),
|
|
67
|
+
|
|
68
|
+
BaseScreen: (props) => (
|
|
69
|
+
<BaseScreen {...props} />
|
|
70
|
+
),
|
|
71
|
+
|
|
25
72
|
FoodMenuScreen: (props) => (
|
|
26
73
|
<FoodMenuScreen
|
|
27
74
|
{...props}
|
package/package.json
CHANGED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./lib/index.js",
|
|
13
13
|
"./components": "./lib/components.js",
|
|
14
|
-
"./globals": "./lib/screen-
|
|
15
|
-
"./hero-screen-registry": "./lib/hero-screen-
|
|
16
|
-
"./
|
|
14
|
+
"./globals": "./lib/screen-registry.js",
|
|
15
|
+
"./hero-screen-registry": "./lib/hero-screen-registry.js",
|
|
16
|
+
"./hooks/use-safe-area-top-inset": "./lib/hooks/use-safe-area-top-inset.js",
|
|
17
|
+
"./lite": "./lib/hero-screen-registry.js",
|
|
17
18
|
"./notifications": "./lib/notifications.js",
|
|
18
19
|
"./notifications-fcm": "./lib/notifications/fcm.js",
|
|
19
|
-
"./screen-
|
|
20
|
+
"./screen-registry": "./lib/screen-registry.js",
|
|
20
21
|
"./screens": "./lib/screens.js"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [],
|
|
@@ -33,5 +34,5 @@
|
|
|
33
34
|
},
|
|
34
35
|
"sideEffects": false,
|
|
35
36
|
"type": "module",
|
|
36
|
-
"version": "0.0.
|
|
37
|
+
"version": "0.0.11"
|
|
37
38
|
}
|
|
File without changes
|