@oneplatformdev/react-native-uikit 1.2.0 → 1.2.3

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/README.md CHANGED
@@ -313,11 +313,22 @@ Both are controlled: pass `value`/`onValueChange` to `Switch` or `checked`/`onVa
313
313
 
314
314
  `Checkbox` defaults to a 22-unit slightly rounded square with a built-in checkmark. Override `size`, state colors, `borderWidth`, and `iconSize`/`iconColor`. Use `shape="circle"` for a radius derived from its size, or `shape="square"` with `borderRadius` (including `0`) for custom corners. Supply an `icon` node to replace the built-in indicator; the consumer owns that node's size and color.
315
315
 
316
+ Use `interactive={false}` when Checkbox is only a visual indicator inside another pressable row. It renders a non-interactive View, does not handle touches, and leaves checked-state accessibility to the parent row. The default remains interactive.
317
+
316
318
  Owned source is available through `npx @oneplatformdev/react-native-uikit add switch` and `npx @oneplatformdev/react-native-uikit add checkbox`. Both are standalone components; Switch uses the existing `react-native-reanimated` peer.
317
319
 
318
320
  ## PhotoPreview and EmptyListView
319
321
 
320
- `PhotoPreview` displays a supplied React Native image source. It defaults to a 120-unit square with the theme's medium radius. Set `width` and/or `height` for a different size; `aspectRatio` derives the missing dimension (width wins if both dimensions and a ratio are supplied). `style` is applied after the base container geometry, and `imageStyle` after the fill-image style. Supply `onPress` to make the image a button, `onRemovePress` for a separate remove control, or `overlay` for caller-owned content. A built-in × is used unless `removeIcon` is provided; supply localized accessibility labels for actions. The component does not open a modal or own image state.
322
+ `PhotoPreview` uses `@d11/react-native-fast-image` for both the thumbnail and optional fullscreen viewer. Install that native peer in the app. It accepts a FastImage source (`{ uri }` or a local `require(...)`) and passes it unchanged to both images, preserving headers, priority, and any explicit cache policy. Without `source.cache`, FastImage uses its own default `immutable` policy (URI-keyed caching); choose `source={{ uri, cache: FastImage.cacheControl.web }}` when HTTP-header-aware caching is needed. The default thumbnail is a 120-unit square with the theme's medium radius. `width`/`height` are design values; `aspectRatio` derives the missing dimension (width wins if both dimensions and a ratio are supplied). `style` applies after container geometry; `imageStyle` applies after the fill-image style. Source, resize-mode, and image-style types follow FastImage rather than React Native `Image`.
323
+
324
+ ```tsx
325
+ <PhotoPreview source={{ uri }} />
326
+ <PhotoPreview source={{ uri }} fullscreen closeAccessibilityLabel={closePhotoLabel} />
327
+ <PhotoPreview source={{ uri }} fullscreen closeIcon={<MyCloseIcon />} loadingIndicator={<MyLoader />} />
328
+ <PhotoPreview source={{ uri }} onRemovePress={onRemove} removeIcon={<MyRemoveIcon />} />
329
+ ```
330
+
331
+ `fullscreen` is opt-in. Pressing opens one built-in Modal with a cached FastImage (`contain` by default), a centered package Loader until load or error, and a close button positioned at the unscaled top safe-area inset plus scaled design spacing. Apps using fullscreen should configure their usual `SafeAreaProvider`. Override the fullscreen background, resize mode, image style, close icon/labels, or loading indicator as needed. With `fullscreen`, `onPress` observes the same thumbnail press after opening; `onFullscreenOpen`/`onFullscreenClose` observe viewer transitions. Without `fullscreen`, `onPress` keeps its prior behavior. The independent remove action never opens fullscreen; `overlay` remains thumbnail-only. A built-in × is used if `removeIcon` or `closeIcon` is omitted. Supply localized accessibility labels for actions.
321
332
 
322
333
  `EmptyListView` centers optional `visual`, `title`, and `description` in available list space. Strings and numbers use themed Typography (`title` and `body` by default); custom React nodes render as supplied. `titleStyle` and `descriptionStyle` apply to built-in text, while custom nodes own their styling. A supplied `action` replaces the built-in Button; otherwise the Button appears only when both `actionLabel` and `onActionPress` are given. The optional `accessibilityLabel` labels the visual without grouping the readable text or action.
323
334
 
@@ -335,7 +346,7 @@ Owned source is available with `npx @oneplatformdev/react-native-uikit add photo
335
346
 
336
347
  `BottomModal` provides the reference-style slide/drag dismissal and keyboard-height-aware positioning. Supply `children`, `onClose`, and optionally `title`, `customHeader`, and a labeled `closeAction` icon. `Keyboard.dismiss()` and the close animation start together; `closeRef` can trigger the same close path externally. The kit does not render app-specific toasts.
337
348
 
338
- Both modes require consumers to install the declared native peers used by these components: `react-native-keyboard-controller`, `react-native-linear-gradient`, `react-native-reanimated`, and `react-native-safe-area-context`. Applications using keyboard-aware or safe-area behavior should configure the providers and native setup required by those libraries. The kit does not bundle those peers or install them through `add`.
349
+ Both modes require consumers to install the declared native peers used by these components: `@d11/react-native-fast-image`, `react-native-keyboard-controller`, `react-native-linear-gradient`, `react-native-reanimated`, and `react-native-safe-area-context`. Applications using keyboard-aware or safe-area behavior should configure the providers and native setup required by those libraries. The kit does not bundle those peers or install them through `add`.
339
350
 
340
351
  ## ConnectionContainer
341
352
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneplatformdev/react-native-uikit",
3
- "version": "1.2.0",
3
+ "version": "1.2.3",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "bin": {
@@ -18,6 +18,7 @@
18
18
  "validate:registry": "node scripts/validate-registry.cjs"
19
19
  },
20
20
  "peerDependencies": {
21
+ "@d11/react-native-fast-image": "^8.13.0",
21
22
  "react": "*",
22
23
  "react-native": "*",
23
24
  "react-native-keyboard-controller": "^1.22.5",
@@ -26,6 +27,7 @@
26
27
  "react-native-safe-area-context": "^5.5.2"
27
28
  },
28
29
  "devDependencies": {
30
+ "@d11/react-native-fast-image": "^8.13.0",
29
31
  "@types/react": "^19.2.0",
30
32
  "react-native-keyboard-controller": "^1.22.5",
31
33
  "react-native-linear-gradient": "^2.8.3",
@@ -58,10 +58,10 @@
58
58
  },
59
59
  "photo-preview": {
60
60
  "folder": "PhotoPreview",
61
- "files": ["ui/PhotoPreview.tsx", "ui/styles.ts", "types/types.ts", "index.ts"],
62
- "componentDependencies": [],
61
+ "files": ["ui/PhotoPreview.tsx", "presenters/usePhotoPreview.ts", "ui/styles.ts", "types/types.ts", "index.ts"],
62
+ "componentDependencies": ["loader"],
63
63
  "foundations": ["useUITheme", "useScaling", "UIColors", "UIRadius", "UISpacing", "ScalingFunctions"],
64
- "dependencies": ["react", "react-native", "@oneplatformdev/react-native-uikit"],
64
+ "dependencies": ["react", "react-native", "@d11/react-native-fast-image", "react-native-safe-area-context", "@oneplatformdev/react-native-uikit"],
65
65
  "rewrites": {
66
66
  "../../../theme": "@oneplatformdev/react-native-uikit",
67
67
  "../../../utils": "@oneplatformdev/react-native-uikit"
@@ -148,6 +148,7 @@
148
148
  "folder": "Loader",
149
149
  "files": ["ui/Loader.tsx", "ui/styles.ts", "types/types.ts", "index.ts"],
150
150
  "componentDependencies": [],
151
+ "publicExports": { "Loader": "ui/Loader.tsx", "LoaderProps": "types/types.ts" },
151
152
  "foundations": ["useUITheme", "UIColors"],
152
153
  "dependencies": ["react", "react-native", "@oneplatformdev/react-native-uikit"],
153
154
  "rewrites": {
@@ -4,6 +4,7 @@ import type { StyleProp, ViewStyle } from 'react-native';
4
4
  export interface CheckboxProps {
5
5
  checked: boolean;
6
6
  onValueChange?: (checked: boolean) => void;
7
+ interactive?: boolean;
7
8
  disabled?: boolean;
8
9
  size?: number;
9
10
  shape?: 'square' | 'circle';
@@ -8,6 +8,7 @@ import type { CheckboxProps } from '../types/types';
8
8
  export const Checkbox = ({
9
9
  checked,
10
10
  onValueChange,
11
+ interactive = true,
11
12
  disabled = false,
12
13
  size = 22,
13
14
  shape = 'square',
@@ -47,8 +48,29 @@ export const Checkbox = ({
47
48
  disabledBackgroundColor, disabledBorderColor, iconSize, iconColor,
48
49
  ]);
49
50
  const onPress = useCallback(() => {
50
- if (!disabled) onValueChange?.(!checked);
51
- }, [checked, disabled, onValueChange]);
51
+ if (interactive && !disabled) onValueChange?.(!checked);
52
+ }, [checked, disabled, interactive, onValueChange]);
53
+
54
+ const content = checked ? (
55
+ <View pointerEvents="none">
56
+ {icon === undefined ? <View style={styles.indicator} />
57
+ : typeof icon === 'string' || typeof icon === 'number' ? <Text>{icon}</Text> : icon}
58
+ </View>
59
+ ) : null;
60
+
61
+ if (!interactive) {
62
+ return (
63
+ <View
64
+ pointerEvents="none"
65
+ accessible={false}
66
+ accessibilityElementsHidden
67
+ importantForAccessibility="no-hide-descendants"
68
+ style={[styles.container, style]}
69
+ >
70
+ {content}
71
+ </View>
72
+ );
73
+ }
52
74
 
53
75
  return (
54
76
  <Pressable
@@ -61,12 +83,7 @@ export const Checkbox = ({
61
83
  onPress={onPress}
62
84
  style={[styles.container, style]}
63
85
  >
64
- {checked ? (
65
- <View pointerEvents="none">
66
- {icon === undefined ? <View style={styles.indicator} />
67
- : typeof icon === 'string' || typeof icon === 'number' ? <Text>{icon}</Text> : icon}
68
- </View>
69
- ) : null}
86
+ {content}
70
87
  </Pressable>
71
88
  );
72
89
  };
@@ -1,2 +1,2 @@
1
- export { Loader } from './ui/Loader';
2
- export type { LoaderProps } from './types/types';
1
+ export * from './ui/Loader';
2
+ export * from './types/types';
@@ -0,0 +1,49 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import type { PhotoPreviewProps } from '../types/types';
3
+
4
+ export const usePhotoPreview = ({
5
+ fullscreen,
6
+ disabled,
7
+ onPress,
8
+ onFullscreenOpen,
9
+ onFullscreenClose,
10
+ }: Pick<PhotoPreviewProps, 'fullscreen' | 'disabled' | 'onPress' | 'onFullscreenOpen' | 'onFullscreenClose'>) => {
11
+ const [visible, setVisible] = useState(false);
12
+
13
+ useEffect(() => {
14
+ if (!fullscreen) setVisible(false);
15
+ }, [fullscreen]);
16
+
17
+ const onThumbnailPress = useCallback(() => {
18
+ if (disabled) return;
19
+ if (fullscreen) {
20
+ setVisible(true);
21
+ onFullscreenOpen?.();
22
+ }
23
+ onPress?.();
24
+ }, [disabled, fullscreen, onFullscreenOpen, onPress]);
25
+
26
+ const onClose = useCallback(() => {
27
+ setVisible(false);
28
+ onFullscreenClose?.();
29
+ }, [onFullscreenClose]);
30
+
31
+ return { visible: fullscreen === true && visible, onThumbnailPress, onClose };
32
+ };
33
+
34
+ type LoadState = 'loading' | 'loaded' | 'error';
35
+
36
+ export const useFullscreenImage = () => {
37
+ const [loadState, setLoadState] = useState<LoadState>('loading');
38
+ const onLoadStart = useCallback(() => setLoadState('loading'), []);
39
+ const onLoad = useCallback(() => setLoadState('loaded'), []);
40
+ const onError = useCallback(() => setLoadState('error'), []);
41
+
42
+ return {
43
+ isLoading: loadState === 'loading',
44
+ isImageVisible: loadState === 'loaded',
45
+ onLoadStart,
46
+ onLoad,
47
+ onError,
48
+ };
49
+ };
@@ -1,13 +1,24 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { ImageResizeMode, ImageSourcePropType, ImageStyle, StyleProp, ViewStyle } from 'react-native';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+ import type { FastImageProps } from '@d11/react-native-fast-image';
3
4
 
4
5
  export interface PhotoPreviewProps {
5
- source: ImageSourcePropType;
6
+ source: NonNullable<FastImageProps['source']>;
6
7
  width?: number;
7
8
  height?: number;
8
9
  aspectRatio?: number;
9
10
  borderRadius?: number;
10
- resizeMode?: ImageResizeMode;
11
+ resizeMode?: FastImageProps['resizeMode'];
12
+ fullscreen?: boolean;
13
+ fullscreenResizeMode?: FastImageProps['resizeMode'];
14
+ fullscreenBackgroundColor?: string;
15
+ fullscreenImageStyle?: FastImageProps['style'];
16
+ closeIcon?: ReactNode;
17
+ closeAccessibilityLabel?: string;
18
+ closeAccessibilityHint?: string;
19
+ loadingIndicator?: ReactNode;
20
+ onFullscreenOpen?: () => void;
21
+ onFullscreenClose?: () => void;
11
22
  overlay?: ReactNode;
12
23
  removeIcon?: ReactNode;
13
24
  onRemovePress?: () => void;
@@ -18,5 +29,5 @@ export interface PhotoPreviewProps {
18
29
  accessibilityHint?: string;
19
30
  disabled?: boolean;
20
31
  style?: StyleProp<ViewStyle>;
21
- imageStyle?: StyleProp<ImageStyle>;
32
+ imageStyle?: FastImageProps['style'];
22
33
  }
@@ -1,17 +1,118 @@
1
1
  import { useMemo } from 'react';
2
- import { Image, Pressable, StyleSheet, Text, View } from 'react-native';
2
+ import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
3
+ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
4
+ import FastImage from '@d11/react-native-fast-image';
5
+ import { Loader } from '../../Loader';
3
6
  import { useUITheme } from '../../../theme';
4
7
  import { useScaling } from '../../../utils';
5
- import { getStyles } from './styles';
8
+ import { useFullscreenImage, usePhotoPreview } from '../presenters/usePhotoPreview';
9
+ import { fastImageStyles, getFullscreenStyles, getStyles } from './styles';
6
10
  import type { PhotoPreviewProps } from '../types/types';
7
11
 
12
+ type FullscreenImageProps = Pick<PhotoPreviewProps, 'source' | 'fullscreenResizeMode' | 'fullscreenImageStyle' | 'loadingIndicator'>
13
+ & { styles: ReturnType<typeof getFullscreenStyles>['styles'] };
14
+
15
+ const FullscreenImage = ({ source, fullscreenResizeMode, fullscreenImageStyle, loadingIndicator, styles }: FullscreenImageProps) => {
16
+ const { isLoading, isImageVisible, onLoadStart, onLoad, onError } = useFullscreenImage();
17
+ const hasSource = typeof source === 'number' || Boolean(source.uri?.trim());
18
+
19
+ return (
20
+ <View style={styles.imageArea}>
21
+ {hasSource ? (
22
+ <FastImage
23
+ source={source}
24
+ resizeMode={fullscreenResizeMode ?? FastImage.resizeMode.contain}
25
+ accessible={false}
26
+ style={[fastImageStyles.fill, fullscreenImageStyle, !isImageVisible && fastImageStyles.hidden]}
27
+ onLoadStart={onLoadStart}
28
+ onLoad={onLoad}
29
+ onError={onError}
30
+ />
31
+ ) : null}
32
+ {hasSource && isLoading ? (
33
+ <View pointerEvents="none" style={styles.loadingOverlay}>
34
+ {loadingIndicator === undefined ? <Loader inline transparent accessibilityLabel="Loading photo" /> : loadingIndicator}
35
+ </View>
36
+ ) : null}
37
+ </View>
38
+ );
39
+ };
40
+
41
+ type FullscreenViewerProps = Pick<PhotoPreviewProps,
42
+ | 'source' | 'fullscreenResizeMode' | 'fullscreenBackgroundColor' | 'fullscreenImageStyle'
43
+ | 'closeIcon' | 'closeAccessibilityLabel' | 'closeAccessibilityHint' | 'loadingIndicator'>
44
+ & { visible: boolean; onClose: () => void };
45
+
46
+ const FullscreenViewer = ({
47
+ source,
48
+ visible,
49
+ onClose,
50
+ fullscreenResizeMode,
51
+ fullscreenBackgroundColor,
52
+ fullscreenImageStyle,
53
+ closeIcon,
54
+ closeAccessibilityLabel,
55
+ closeAccessibilityHint,
56
+ loadingIndicator,
57
+ }: FullscreenViewerProps) => {
58
+ const { colors, spacing } = useUITheme();
59
+ const scaling = useScaling();
60
+ const { top } = useSafeAreaInsets();
61
+ const { styles, closeHitSlop } = useMemo(
62
+ () => getFullscreenStyles(colors, spacing, scaling, top, fullscreenBackgroundColor),
63
+ [colors, spacing, scaling, top, fullscreenBackgroundColor],
64
+ );
65
+ const sourceKey = typeof source === 'number' ? `asset:${source}` : JSON.stringify(source);
66
+
67
+ return (
68
+ <Modal visible={visible} animationType="fade" statusBarTranslucent onRequestClose={onClose}>
69
+ <SafeAreaView edges={['left', 'right', 'bottom']} style={styles.fullscreen} accessibilityViewIsModal>
70
+ <View style={styles.toolbar}>
71
+ <Pressable
72
+ accessibilityRole="button"
73
+ accessibilityLabel={closeAccessibilityLabel ?? 'Close photo'}
74
+ accessibilityHint={closeAccessibilityHint}
75
+ hitSlop={closeHitSlop}
76
+ onPress={onClose}
77
+ style={styles.close}
78
+ >
79
+ {closeIcon === undefined ? <Text style={styles.closeText}>×</Text>
80
+ : typeof closeIcon === 'string' || typeof closeIcon === 'number'
81
+ ? <Text style={styles.closeText}>{closeIcon}</Text> : closeIcon}
82
+ </Pressable>
83
+ </View>
84
+ {visible ? (
85
+ <FullscreenImage
86
+ key={sourceKey}
87
+ source={source}
88
+ fullscreenResizeMode={fullscreenResizeMode}
89
+ fullscreenImageStyle={fullscreenImageStyle}
90
+ loadingIndicator={loadingIndicator}
91
+ styles={styles}
92
+ />
93
+ ) : null}
94
+ </SafeAreaView>
95
+ </Modal>
96
+ );
97
+ };
98
+
8
99
  export const PhotoPreview = ({
9
100
  source,
10
101
  width,
11
102
  height,
12
103
  aspectRatio,
13
104
  borderRadius,
14
- resizeMode = 'cover',
105
+ resizeMode = FastImage.resizeMode.cover,
106
+ fullscreen = false,
107
+ fullscreenResizeMode,
108
+ fullscreenBackgroundColor,
109
+ fullscreenImageStyle,
110
+ closeIcon,
111
+ closeAccessibilityLabel,
112
+ closeAccessibilityHint,
113
+ loadingIndicator,
114
+ onFullscreenOpen,
115
+ onFullscreenClose,
15
116
  overlay,
16
117
  removeIcon,
17
118
  onRemovePress,
@@ -30,28 +131,34 @@ export const PhotoPreview = ({
30
131
  () => getStyles(colors, radius, spacing, scaling, width, height, aspectRatio, borderRadius),
31
132
  [colors, radius, spacing, scaling, width, height, aspectRatio, borderRadius],
32
133
  );
134
+ const { visible, onThumbnailPress, onClose } = usePhotoPreview({
135
+ fullscreen, disabled, onPress, onFullscreenOpen, onFullscreenClose,
136
+ });
137
+ const isPressable = fullscreen || onPress !== undefined;
33
138
  const image = (
34
- <Image
35
- source={source}
36
- resizeMode={resizeMode}
37
- accessible={!onPress}
38
- accessibilityRole={onPress ? undefined : 'image'}
39
- accessibilityLabel={onPress ? undefined : accessibilityLabel}
40
- accessibilityHint={onPress ? undefined : accessibilityHint}
41
- style={[styles.image, imageStyle]}
42
- />
139
+ <View style={styles.image}>
140
+ <FastImage
141
+ source={source}
142
+ resizeMode={resizeMode}
143
+ accessible={!isPressable}
144
+ accessibilityRole={isPressable ? undefined : 'image'}
145
+ accessibilityLabel={isPressable ? undefined : accessibilityLabel}
146
+ accessibilityHint={isPressable ? undefined : accessibilityHint}
147
+ style={[fastImageStyles.fill, imageStyle]}
148
+ />
149
+ </View>
43
150
  );
44
151
 
45
152
  return (
46
153
  <View style={[styles.container, style]}>
47
- {onPress ? (
154
+ {isPressable ? (
48
155
  <Pressable
49
156
  accessibilityRole="button"
50
- accessibilityLabel={accessibilityLabel}
157
+ accessibilityLabel={accessibilityLabel ?? 'Open photo'}
51
158
  accessibilityHint={accessibilityHint}
52
159
  accessibilityState={{ disabled }}
53
160
  disabled={disabled}
54
- onPress={onPress}
161
+ onPress={onThumbnailPress}
55
162
  style={StyleSheet.absoluteFill}
56
163
  >
57
164
  {image}
@@ -78,6 +185,20 @@ export const PhotoPreview = ({
78
185
  ? <Text style={styles.removeText}>{removeIcon}</Text> : removeIcon}
79
186
  </Pressable>
80
187
  ) : null}
188
+ {fullscreen ? (
189
+ <FullscreenViewer
190
+ source={source}
191
+ visible={visible}
192
+ onClose={onClose}
193
+ fullscreenResizeMode={fullscreenResizeMode}
194
+ fullscreenBackgroundColor={fullscreenBackgroundColor}
195
+ fullscreenImageStyle={fullscreenImageStyle}
196
+ closeIcon={closeIcon}
197
+ closeAccessibilityLabel={closeAccessibilityLabel}
198
+ closeAccessibilityHint={closeAccessibilityHint}
199
+ loadingIndicator={loadingIndicator}
200
+ />
201
+ ) : null}
81
202
  </View>
82
203
  );
83
204
  };
@@ -1,4 +1,6 @@
1
1
  import { StyleSheet } from 'react-native';
2
+ import type { ViewStyle } from 'react-native';
3
+ import type { ImageStyle as FastImageImageStyle } from '@d11/react-native-fast-image';
2
4
  import type { UIColors, UIRadius, UISpacing } from '../../../theme';
3
5
  import type { ScalingFunctions } from '../../../utils';
4
6
  import type { PhotoPreviewProps } from '../types/types';
@@ -6,6 +8,13 @@ import type { PhotoPreviewProps } from '../types/types';
6
8
  const positiveOrUndefined = (value: number | undefined) =>
7
9
  value !== undefined && Number.isFinite(value) && value > 0 ? value : undefined;
8
10
 
11
+ type CompatibleFastImageStyle = FastImageImageStyle & Pick<ViewStyle, 'width' | 'height' | 'opacity'>;
12
+
13
+ export const fastImageStyles: Record<'fill' | 'hidden', CompatibleFastImageStyle> = {
14
+ fill: { width: '100%', height: '100%' },
15
+ hidden: { opacity: 0 },
16
+ };
17
+
9
18
  export const getStyles = (
10
19
  colors: UIColors,
11
20
  radius: UIRadius,
@@ -66,3 +75,45 @@ export const getStyles = (
66
75
 
67
76
  return { styles, removeHitSlop };
68
77
  };
78
+
79
+ export const getFullscreenStyles = (
80
+ colors: UIColors,
81
+ spacing: UISpacing,
82
+ scaling: ScalingFunctions,
83
+ topInset: number,
84
+ backgroundColor?: string,
85
+ ) => {
86
+ const styles = StyleSheet.create({
87
+ fullscreen: {
88
+ flex: 1,
89
+ backgroundColor: backgroundColor ?? colors.text,
90
+ },
91
+ toolbar: {
92
+ alignItems: 'flex-end',
93
+ paddingHorizontal: scaling.scaleHorizontal(spacing.lg),
94
+ paddingTop: topInset + scaling.scaleVertical(spacing.sm),
95
+ },
96
+ close: {
97
+ width: scaling.scaleHorizontal(40),
98
+ height: scaling.scaleHorizontal(40),
99
+ alignItems: 'center',
100
+ justifyContent: 'center',
101
+ },
102
+ closeText: {
103
+ color: colors.onPrimary,
104
+ fontSize: scaling.scaleFontSize(24),
105
+ lineHeight: scaling.scaleLineHeight(28),
106
+ },
107
+ imageArea: {
108
+ flex: 1,
109
+ width: '100%',
110
+ },
111
+ loadingOverlay: {
112
+ ...StyleSheet.absoluteFill,
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ },
116
+ });
117
+
118
+ return { styles, closeHitSlop: scaling.scaleHorizontal(spacing.xs) };
119
+ };