@oneplatformdev/react-native-uikit 0.0.0-stage → 1.1.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.
Files changed (68) hide show
  1. package/README.md +376 -2
  2. package/bin/cli.cjs +372 -0
  3. package/package.json +34 -4
  4. package/registry/manifest.json +113 -0
  5. package/src/components/BottomModal/index.ts +2 -0
  6. package/src/components/BottomModal/presenters/useBottomModal.ts +114 -0
  7. package/src/components/BottomModal/presenters/useBottomModalInsets.ts +19 -0
  8. package/src/components/BottomModal/types/types.ts +23 -0
  9. package/src/components/BottomModal/ui/BottomModal.tsx +110 -0
  10. package/src/components/BottomModal/ui/styles.ts +115 -0
  11. package/src/components/Button/index.ts +8 -0
  12. package/src/components/Button/presenters/useButton.ts +72 -0
  13. package/src/components/Button/types/types.ts +39 -0
  14. package/src/components/Button/ui/Button.tsx +121 -0
  15. package/src/components/Button/ui/styles.ts +82 -0
  16. package/src/components/ConnectionContainer/index.ts +2 -0
  17. package/src/components/ConnectionContainer/presenters/useConnectionContainer.ts +77 -0
  18. package/src/components/ConnectionContainer/types/types.ts +14 -0
  19. package/src/components/ConnectionContainer/ui/ConnectionContainer.tsx +51 -0
  20. package/src/components/ConnectionContainer/ui/styles.ts +35 -0
  21. package/src/components/CustomAlert/index.ts +2 -0
  22. package/src/components/CustomAlert/presenters/useCustomAlert.ts +24 -0
  23. package/src/components/CustomAlert/types/types.ts +21 -0
  24. package/src/components/CustomAlert/ui/CustomAlert.tsx +79 -0
  25. package/src/components/CustomAlert/ui/styles.ts +71 -0
  26. package/src/components/HeaderWithBackButton/index.ts +2 -0
  27. package/src/components/HeaderWithBackButton/presenters/useHeaderWithBackButton.ts +17 -0
  28. package/src/components/HeaderWithBackButton/types/types.ts +22 -0
  29. package/src/components/HeaderWithBackButton/ui/HeaderWithBackButton.tsx +91 -0
  30. package/src/components/HeaderWithBackButton/ui/styles.ts +58 -0
  31. package/src/components/Input/index.ts +8 -0
  32. package/src/components/Input/presenters/useInput.ts +104 -0
  33. package/src/components/Input/types/types.ts +36 -0
  34. package/src/components/Input/ui/Input.tsx +161 -0
  35. package/src/components/Input/ui/styles.ts +80 -0
  36. package/src/components/Loader/index.ts +2 -0
  37. package/src/components/Loader/types/types.ts +12 -0
  38. package/src/components/Loader/ui/Loader.tsx +29 -0
  39. package/src/components/Loader/ui/styles.ts +14 -0
  40. package/src/components/Logger/index.ts +2 -0
  41. package/src/components/Logger/presenters/useLogger.ts +13 -0
  42. package/src/components/Logger/types/types.ts +56 -0
  43. package/src/components/Logger/ui/Logger.tsx +85 -0
  44. package/src/components/Logger/ui/components/LoggerItem.tsx +117 -0
  45. package/src/components/Logger/ui/styles.ts +121 -0
  46. package/src/components/Logger/utils/groupLoggerEntries.ts +40 -0
  47. package/src/components/Logger/utils/loggerPresentation.ts +69 -0
  48. package/src/components/ScreenContainer/index.ts +2 -0
  49. package/src/components/ScreenContainer/presenters/useScreenContainer.ts +12 -0
  50. package/src/components/ScreenContainer/types/types.ts +19 -0
  51. package/src/components/ScreenContainer/ui/ScreenContainer.tsx +83 -0
  52. package/src/components/ScreenContainer/ui/styles.ts +31 -0
  53. package/src/components/Typography/index.ts +2 -0
  54. package/src/components/Typography/types/types.ts +14 -0
  55. package/src/components/Typography/ui/Typography.tsx +37 -0
  56. package/src/components/Typography/ui/styles.ts +26 -0
  57. package/src/index.ts +13 -0
  58. package/src/provider/LocalizationContext.tsx +14 -0
  59. package/src/provider/ModeContext.tsx +9 -0
  60. package/src/provider/index.ts +12 -0
  61. package/src/provider/types.ts +31 -0
  62. package/src/theme/ThemeProvider.tsx +7 -0
  63. package/src/theme/UIProvider.tsx +60 -0
  64. package/src/theme/createTheme.ts +66 -0
  65. package/src/theme/defaultTheme.ts +215 -0
  66. package/src/theme/index.ts +4 -0
  67. package/src/theme/types.ts +265 -0
  68. package/src/utils/index.ts +94 -0
@@ -0,0 +1,161 @@
1
+ import React, { forwardRef, memo, useMemo } from 'react';
2
+ import { Pressable, TextInput, View } from 'react-native';
3
+ import { Typography } from '../../Typography';
4
+ import { useUITheme } from '../../../theme';
5
+ import { useScaling } from '../../../utils';
6
+ import { useInput } from '../presenters/useInput';
7
+ import type { InputProps, InputRef } from '../types/types';
8
+ import { getPasswordHitSlop, getStyles } from './styles';
9
+
10
+ const InputComponent = forwardRef<InputRef, InputProps>(
11
+ (
12
+ {
13
+ accessibilityHint,
14
+ accessibilityLabel,
15
+ accessibilityState,
16
+ containerStyle,
17
+ disabled = false,
18
+ editable,
19
+ endAccessory,
20
+ errorText,
21
+ errorTextProps,
22
+ helperText,
23
+ helperTextProps,
24
+ inputContainerStyle,
25
+ invalid = false,
26
+ label,
27
+ labelProps,
28
+ multiline,
29
+ onBlur,
30
+ onFocus,
31
+ passwordToggle,
32
+ placeholderTextColor,
33
+ secureTextEntry,
34
+ startAccessory,
35
+ style,
36
+ ...nativeProps
37
+ },
38
+ ref,
39
+ ) => {
40
+ const {
41
+ canTogglePassword,
42
+ errorColor,
43
+ errorVariant,
44
+ helperColor,
45
+ helperVariant,
46
+ isFocused,
47
+ isInvalid,
48
+ labelColor,
49
+ labelVariant,
50
+ onInputBlur,
51
+ onInputFocus,
52
+ onPasswordVisibilityToggle,
53
+ passwordAction,
54
+ resolvedAccessibilityHint,
55
+ resolvedAccessibilityLabel,
56
+ resolvedAccessibilityState,
57
+ resolvedEditable,
58
+ resolvedPlaceholderTextColor,
59
+ resolvedSecureTextEntry,
60
+ showHelperText,
61
+ } = useInput({
62
+ accessibilityHint,
63
+ accessibilityLabel,
64
+ accessibilityState,
65
+ disabled,
66
+ editable,
67
+ errorText,
68
+ errorTextProps,
69
+ helperText,
70
+ helperTextProps,
71
+ invalid,
72
+ label,
73
+ labelProps,
74
+ onBlur,
75
+ onFocus,
76
+ passwordToggle,
77
+ placeholderTextColor,
78
+ secureTextEntry,
79
+ });
80
+
81
+ const { colors, fonts, input } = useUITheme();
82
+ const scaling = useScaling();
83
+ const styles = useMemo(
84
+ () => getStyles(colors, fonts, input, scaling, isFocused, isInvalid, disabled),
85
+ [colors, disabled, fonts, input, isFocused, isInvalid, scaling],
86
+ );
87
+ const textInputStyle = [styles.text, multiline && styles.multilineText, style];
88
+ const passwordHitSlop = canTogglePassword && passwordAction ? getPasswordHitSlop(scaling) : undefined;
89
+
90
+ return (
91
+ <View style={[styles.container, containerStyle]}>
92
+ {label ? (
93
+ <View style={styles.labelContainer}>
94
+ <Typography
95
+ {...labelProps}
96
+ text={label}
97
+ variant={labelVariant}
98
+ color={labelColor}
99
+ style={labelProps?.style}
100
+ />
101
+ </View>
102
+ ) : null}
103
+
104
+ <View style={[styles.field, inputContainerStyle]}>
105
+ {startAccessory != null ? <View style={styles.startAccessory}>{startAccessory}</View> : null}
106
+ <TextInput
107
+ {...nativeProps}
108
+ ref={ref}
109
+ accessibilityLabel={resolvedAccessibilityLabel}
110
+ accessibilityHint={resolvedAccessibilityHint}
111
+ accessibilityState={resolvedAccessibilityState}
112
+ editable={resolvedEditable}
113
+ multiline={multiline}
114
+ placeholderTextColor={resolvedPlaceholderTextColor}
115
+ secureTextEntry={resolvedSecureTextEntry}
116
+ onFocus={onInputFocus}
117
+ onBlur={onInputBlur}
118
+ style={textInputStyle}
119
+ />
120
+ {endAccessory != null ? <View style={styles.endAccessory}>{endAccessory}</View> : null}
121
+ {canTogglePassword && passwordAction ? (
122
+ <Pressable
123
+ accessibilityRole="button"
124
+ accessibilityLabel={passwordAction.label}
125
+ disabled={disabled}
126
+ hitSlop={passwordHitSlop}
127
+ onPress={onPasswordVisibilityToggle}
128
+ style={styles.passwordToggle}
129
+ >
130
+ {passwordAction.icon}
131
+ </Pressable>
132
+ ) : null}
133
+ </View>
134
+
135
+ {errorText ? (
136
+ <Typography
137
+ {...errorTextProps}
138
+ text={errorText}
139
+ variant={errorVariant}
140
+ color={errorColor}
141
+ accessibilityRole="alert"
142
+ accessibilityLiveRegion="polite"
143
+ style={[styles.supportingText, errorTextProps?.style]}
144
+ />
145
+ ) : showHelperText && helperText ? (
146
+ <Typography
147
+ {...helperTextProps}
148
+ text={helperText}
149
+ variant={helperVariant}
150
+ color={helperColor}
151
+ style={[styles.supportingText, helperTextProps?.style]}
152
+ />
153
+ ) : null}
154
+ </View>
155
+ );
156
+ },
157
+ );
158
+
159
+ InputComponent.displayName = 'Input';
160
+
161
+ export const Input = memo(InputComponent);
@@ -0,0 +1,80 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { TextStyle, ViewStyle } from 'react-native';
3
+ import type { UIColors, UIFonts, UIInputTheme } from '../../../theme';
4
+ import type { ScalingFunctions } from '../../../utils';
5
+
6
+ export const getStyles = (
7
+ colors: UIColors,
8
+ fonts: UIFonts,
9
+ input: UIInputTheme,
10
+ scaling: ScalingFunctions,
11
+ focused: boolean,
12
+ invalid: boolean,
13
+ disabled: boolean,
14
+ ) => {
15
+ const { geometry, typography, colors: tokens } = input;
16
+ const border = disabled
17
+ ? tokens.disabledBorder
18
+ : invalid
19
+ ? tokens.errorBorder
20
+ : focused
21
+ ? tokens.focusedBorder
22
+ : tokens.border;
23
+ const field: ViewStyle = {
24
+ minHeight: scaling.scaleVertical(geometry.minHeight),
25
+ flexDirection: 'row',
26
+ alignItems: 'center',
27
+ paddingHorizontal: scaling.scaleHorizontal(geometry.paddingHorizontal),
28
+ borderWidth: scaling.scaleHorizontal(geometry.borderWidth),
29
+ borderColor: colors[border],
30
+ borderRadius: scaling.scaleHorizontal(geometry.radius),
31
+ backgroundColor: colors[disabled ? tokens.disabledBackground : tokens.background],
32
+ };
33
+ const text: TextStyle = {
34
+ flex: 1,
35
+ minWidth: 0,
36
+ minHeight: scaling.scaleVertical(geometry.textMinHeight),
37
+ paddingVertical: 0,
38
+ ...fonts[typography.inputFont],
39
+ fontSize: scaling.scaleFontSize(typography.inputFontSize),
40
+ includeFontPadding: typography.includeFontPadding,
41
+ color: colors[disabled ? tokens.disabledText : tokens.text],
42
+ };
43
+
44
+ const styles = StyleSheet.create({
45
+ container: {
46
+ marginBottom: scaling.scaleVertical(geometry.marginBottom),
47
+ },
48
+ labelContainer: {
49
+ marginBottom: scaling.scaleVertical(geometry.labelGap),
50
+ },
51
+ field,
52
+ text,
53
+ multilineText: {
54
+ textAlignVertical: 'top',
55
+ paddingVertical: scaling.scaleVertical(geometry.multilinePaddingVertical),
56
+ },
57
+ startAccessory: {
58
+ alignItems: 'center',
59
+ justifyContent: 'center',
60
+ marginEnd: scaling.scaleHorizontal(geometry.startAccessoryGap),
61
+ },
62
+ endAccessory: {
63
+ alignItems: 'center',
64
+ justifyContent: 'center',
65
+ marginStart: scaling.scaleHorizontal(geometry.endAccessoryGap),
66
+ },
67
+ passwordToggle: {
68
+ alignItems: 'center',
69
+ justifyContent: 'center',
70
+ marginStart: scaling.scaleHorizontal(geometry.passwordToggleGap),
71
+ },
72
+ supportingText: {
73
+ marginTop: scaling.scaleVertical(geometry.supportingGap),
74
+ },
75
+ });
76
+
77
+ return styles;
78
+ };
79
+
80
+ export const getPasswordHitSlop = (scaling: ScalingFunctions) => scaling.scaleHorizontal(12);
@@ -0,0 +1,2 @@
1
+ export { Loader } from './ui/Loader';
2
+ export type { LoaderProps } from './types/types';
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ActivityIndicatorProps, StyleProp, ViewStyle } from 'react-native';
3
+
4
+ export interface LoaderProps {
5
+ indicator?: ReactNode;
6
+ inline?: boolean;
7
+ transparent?: boolean;
8
+ color?: ActivityIndicatorProps['color'];
9
+ size?: ActivityIndicatorProps['size'];
10
+ accessibilityLabel?: string;
11
+ style?: StyleProp<ViewStyle>;
12
+ }
@@ -0,0 +1,29 @@
1
+ import React, { useMemo } from 'react';
2
+ import { ActivityIndicator, View } from 'react-native';
3
+ import { useUITheme } from '../../../theme';
4
+ import { getStyles } from './styles';
5
+ import type { LoaderProps } from '../types/types';
6
+
7
+ export const Loader = ({
8
+ indicator,
9
+ inline = false,
10
+ transparent = false,
11
+ color,
12
+ size = 'large',
13
+ accessibilityLabel,
14
+ style,
15
+ }: LoaderProps) => {
16
+ const { colors } = useUITheme();
17
+ const styles = useMemo(() => getStyles(colors, inline, transparent), [colors, inline, transparent]);
18
+
19
+ return (
20
+ <View
21
+ accessibilityRole="progressbar"
22
+ accessibilityLabel={accessibilityLabel}
23
+ accessibilityState={{ busy: true }}
24
+ style={[styles.container, style]}
25
+ >
26
+ {indicator === undefined ? <ActivityIndicator color={color ?? colors.primary} size={size} /> : indicator}
27
+ </View>
28
+ );
29
+ };
@@ -0,0 +1,14 @@
1
+ import { StyleSheet } from 'react-native';
2
+ import type { UIColors } from '../../../theme';
3
+
4
+ export const getStyles = (colors: UIColors, inline: boolean, transparent: boolean) => StyleSheet.create({
5
+ container: {
6
+ zIndex: 5,
7
+ position: inline ? 'relative' : 'absolute',
8
+ width: '100%',
9
+ height: inline ? undefined : '100%',
10
+ backgroundColor: transparent ? colors.transparent : colors.background,
11
+ alignItems: 'center',
12
+ justifyContent: 'center',
13
+ },
14
+ });
@@ -0,0 +1,2 @@
1
+ export { Logger } from './ui/Logger';
2
+ export type { LoggerEntry, LoggerEntryType, LoggerHttpMetadata, LoggerRequestData, LoggerLabels, LoggerProps } from './types/types';
@@ -0,0 +1,13 @@
1
+ import { useCallback, useMemo, useState } from 'react';
2
+ import { groupLoggerEntries, toggleExpandedEntryId } from '../utils/groupLoggerEntries';
3
+ import type { LoggerEntry } from '../types/types';
4
+
5
+ export const useLogger = (logs: readonly LoggerEntry[]) => {
6
+ const items = useMemo(() => groupLoggerEntries(logs), [logs]);
7
+ const [expandedEntryIds, setExpandedEntryIds] = useState<Set<string>>(() => new Set());
8
+ const onToggleEntry = useCallback((id: string) => {
9
+ setExpandedEntryIds(previous => toggleExpandedEntryId(previous, id));
10
+ }, []);
11
+
12
+ return { items, expandedEntryIds, onToggleEntry };
13
+ };
@@ -0,0 +1,56 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { StyleProp, ViewStyle } from 'react-native';
3
+
4
+ export type LoggerEntryType = 'request' | 'response' | 'error' | 'library' | 'info' | 'warning';
5
+
6
+ export interface LoggerHttpMetadata {
7
+ /** Unique per HTTP exchange, including concurrent calls to the same endpoint. */
8
+ correlationId: string;
9
+ phase: 'request' | 'response';
10
+ status?: number;
11
+ durationMs?: number;
12
+ }
13
+
14
+ /** Sanitized, display-ready request data supplied by the application. */
15
+ export interface LoggerRequestData {
16
+ params?: string;
17
+ body?: string;
18
+ }
19
+
20
+ export interface LoggerEntry {
21
+ id: string;
22
+ type: LoggerEntryType;
23
+ name: string;
24
+ message: string;
25
+ http?: LoggerHttpMetadata;
26
+ requestData?: LoggerRequestData;
27
+ }
28
+
29
+ export interface LoggerLabels {
30
+ title: string;
31
+ clear: string;
32
+ close: string;
33
+ empty: string;
34
+ request: string;
35
+ response: string;
36
+ params: string;
37
+ body: string;
38
+ payload: string;
39
+ status: string;
40
+ copy: string;
41
+ typeLabels?: Partial<Record<LoggerEntryType, string>>;
42
+ }
43
+
44
+ export interface LoggerProps {
45
+ /** Pass entries newest-first. Storage and retention remain application-owned. */
46
+ logs: readonly LoggerEntry[];
47
+ visible: boolean;
48
+ onClose: () => void;
49
+ onOpen?: () => void;
50
+ onClear?: () => void;
51
+ onCopyEntry?: (entry: LoggerEntry, text: string) => void | Promise<void>;
52
+ labels: LoggerLabels;
53
+ openIcon?: ReactNode;
54
+ copyIcon?: ReactNode;
55
+ style?: StyleProp<ViewStyle>;
56
+ }
@@ -0,0 +1,85 @@
1
+ import React, { useMemo } from 'react';
2
+ import { FlatList, Modal, TouchableOpacity, View } from 'react-native';
3
+ import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
4
+ import { useUITheme } from '../../../theme';
5
+ import { useScaling } from '../../../utils';
6
+ import { Typography } from '../../Typography';
7
+ import { LoggerItem } from './components/LoggerItem';
8
+ import { getStyles } from './styles';
9
+ import { useLogger } from '../presenters/useLogger';
10
+ import type { LoggerPresentationItem } from '../utils/groupLoggerEntries';
11
+ import type { LoggerProps } from '../types/types';
12
+
13
+ export const Logger = ({ logs, visible, onClose, onOpen, onClear, onCopyEntry, labels, openIcon, copyIcon, style }: LoggerProps) => {
14
+ const { colors, spacing, radius } = useUITheme();
15
+ const scaling = useScaling();
16
+ const styles = useMemo(() => getStyles(colors, spacing, radius, scaling), [colors, spacing, radius, scaling]);
17
+ const { items, expandedEntryIds, onToggleEntry } = useLogger(logs);
18
+
19
+ return (
20
+ <>
21
+ {onOpen ? (
22
+ <TouchableOpacity
23
+ accessibilityRole="button"
24
+ accessibilityLabel={labels.title}
25
+ onPress={onOpen}
26
+ style={styles.launcher}
27
+ >
28
+ {openIcon ?? <Typography text={labels.title} variant="label" />}
29
+ </TouchableOpacity>
30
+ ) : null}
31
+
32
+ <Modal visible={visible} onRequestClose={onClose} animationType="slide">
33
+ <SafeAreaProvider>
34
+ <SafeAreaView style={[styles.container, style]}>
35
+ <View style={styles.header}>
36
+ <Typography text={labels.title} variant="title" style={styles.headerTitle} />
37
+ {onClear ? (
38
+ <TouchableOpacity
39
+ accessibilityRole="button"
40
+ accessibilityLabel={labels.clear}
41
+ onPress={onClear}
42
+ style={styles.headerAction}
43
+ >
44
+ <Typography text={labels.clear} variant="label" style={styles.actionText} />
45
+ </TouchableOpacity>
46
+ ) : null}
47
+ <TouchableOpacity
48
+ accessibilityRole="button"
49
+ accessibilityLabel={labels.close}
50
+ onPress={onClose}
51
+ style={styles.headerAction}
52
+ >
53
+ <Typography text={labels.close} variant="label" style={styles.actionText} />
54
+ </TouchableOpacity>
55
+ </View>
56
+
57
+ <FlatList<LoggerPresentationItem>
58
+ data={items}
59
+ keyExtractor={item => item.key}
60
+ renderItem={({ item }) => (
61
+ <LoggerItem
62
+ item={item}
63
+ labels={labels}
64
+ colors={colors}
65
+ styles={styles}
66
+ expandedEntryIds={expandedEntryIds}
67
+ onToggleEntry={onToggleEntry}
68
+ onCopyEntry={onCopyEntry}
69
+ copyIcon={copyIcon}
70
+ />
71
+ )}
72
+ style={styles.list}
73
+ contentContainerStyle={styles.listContent}
74
+ ListEmptyComponent={
75
+ <View style={styles.empty}>
76
+ <Typography text={labels.empty} variant="body" color="textMuted" />
77
+ </View>
78
+ }
79
+ />
80
+ </SafeAreaView>
81
+ </SafeAreaProvider>
82
+ </Modal>
83
+ </>
84
+ );
85
+ };
@@ -0,0 +1,117 @@
1
+ import React from 'react';
2
+ import { Text, TouchableOpacity, View } from 'react-native';
3
+ import type { UIColors } from '../../../../theme';
4
+ import { Typography } from '../../../Typography';
5
+ import type { LoggerPresentationItem } from '../../utils/groupLoggerEntries';
6
+ import {
7
+ getCopyableText,
8
+ getExpandablePayload,
9
+ getLoggerSectionContext,
10
+ getRequestParams,
11
+ getResponseDurationSuffix,
12
+ getResponseSummary,
13
+ getStatusColorKey,
14
+ getVisibleSectionText,
15
+ shouldRenderLoggerSection,
16
+ } from '../../utils/loggerPresentation';
17
+ import type { LoggerEntry, LoggerLabels, LoggerProps } from '../../types/types';
18
+ import type { getStyles } from '../styles';
19
+
20
+ interface LoggerItemProps {
21
+ item: LoggerPresentationItem;
22
+ labels: LoggerLabels;
23
+ colors: UIColors;
24
+ styles: ReturnType<typeof getStyles>;
25
+ expandedEntryIds: ReadonlySet<string>;
26
+ onToggleEntry: (id: string) => void;
27
+ onCopyEntry: LoggerProps['onCopyEntry'];
28
+ copyIcon: LoggerProps['copyIcon'];
29
+ }
30
+
31
+ const sectionColor = (entry: LoggerEntry, colors: UIColors): string => {
32
+ if (entry.type === 'error' || (entry.http?.status !== undefined && entry.http.status >= 400)) return colors.error;
33
+ if (entry.type === 'warning') return colors.warning;
34
+ if (entry.http?.phase === 'response' || entry.type === 'response') return colors.success;
35
+ if (entry.http?.phase === 'request' || entry.type === 'request') return colors.primary;
36
+ return colors.icon;
37
+ };
38
+
39
+ export const LoggerItem = ({ item, labels, colors, styles, expandedEntryIds, onToggleEntry, onCopyEntry, copyIcon }: LoggerItemProps) => {
40
+ const entries = item.kind === 'exchange'
41
+ ? [item.request, item.response].filter((entry): entry is LoggerEntry => entry !== undefined && shouldRenderLoggerSection(entry, labels))
42
+ : shouldRenderLoggerSection(item.entry, labels) ? [item.entry] : [];
43
+ const name = item.kind === 'exchange' ? item.request?.name ?? item.response?.name : item.entry.name;
44
+
45
+ return (
46
+ <View style={styles.card}>
47
+ <Typography text={name ?? ''} variant="label" weight="bold" style={styles.cardTitle} />
48
+ {entries.map((entry, index) => {
49
+ const expanded = expandedEntryIds.has(entry.id);
50
+ const payload = getExpandablePayload(entry, labels);
51
+ const hasPayload = payload !== undefined;
52
+ const requestParams = getRequestParams(entry, labels);
53
+ const copyText = onCopyEntry ? getCopyableText(entry, labels) : undefined;
54
+ const context = getLoggerSectionContext(entry, labels);
55
+ const summary = getResponseSummary(entry, labels);
56
+ const visibleText = getVisibleSectionText(entry, labels);
57
+ const accessibilityLabel = `${context} ${entry.name}${summary ? ` ${summary}` : ''}`;
58
+ const status = entry.http?.phase === 'response' ? entry.http.status : undefined;
59
+ const visibleLabel = visibleText ? status === undefined ? (
60
+ <Typography text={visibleText} variant="caption" weight="medium" style={styles.sectionTitle} />
61
+ ) : (
62
+ <Typography variant="caption" weight="medium" style={styles.sectionTitle}>
63
+ {`${labels.status}: `}
64
+ <Text style={{ color: colors[getStatusColorKey(status)] }}>{status}</Text>
65
+ {getResponseDurationSuffix(entry)}
66
+ </Typography>
67
+ ) : <View style={styles.sectionTitle} />;
68
+ const copyButton = onCopyEntry && copyText !== undefined ? (
69
+ <TouchableOpacity
70
+ accessibilityRole="button"
71
+ accessibilityLabel={`${labels.copy} ${accessibilityLabel}`}
72
+ onPress={() => { onCopyEntry(entry, copyText); }}
73
+ style={requestParams ? styles.copyButtonTop : styles.copyButton}
74
+ >
75
+ {copyIcon ?? <Typography text={labels.copy} variant="caption" />}
76
+ </TouchableOpacity>
77
+ ) : null;
78
+ const showRow = hasPayload || !!visibleText || (!requestParams && copyButton !== null);
79
+ return (
80
+ <React.Fragment key={entry.id}>
81
+ {index > 0 ? <View style={styles.divider} /> : null}
82
+ <View style={[styles.section, { borderLeftColor: sectionColor(entry, colors) }]}>
83
+ {requestParams ? (
84
+ <View style={styles.paramsRow}>
85
+ <Typography text={requestParams} variant="caption" selectable style={[styles.message, styles.paramsText]} />
86
+ {copyButton}
87
+ </View>
88
+ ) : null}
89
+ {showRow ? (
90
+ <View style={styles.sectionRow}>
91
+ {hasPayload ? (
92
+ <TouchableOpacity
93
+ accessibilityRole="button"
94
+ accessibilityLabel={accessibilityLabel}
95
+ accessibilityState={{ expanded }}
96
+ onPress={() => onToggleEntry(entry.id)}
97
+ style={styles.sectionButton}
98
+ >
99
+ {visibleLabel}
100
+ <Typography text={expanded ? '−' : '+'} variant="label" />
101
+ </TouchableOpacity>
102
+ ) : visibleText ? (
103
+ <View accessible accessibilityLabel={accessibilityLabel} style={styles.sectionButton}>
104
+ {visibleLabel}
105
+ </View>
106
+ ) : <View style={styles.sectionTitle} />}
107
+ {!requestParams ? copyButton : null}
108
+ </View>
109
+ ) : requestParams ? null : <View accessible accessibilityLabel={accessibilityLabel} style={styles.emptySection} />}
110
+ {expanded && hasPayload ? <Typography text={payload} variant="body" selectable style={styles.message} /> : null}
111
+ </View>
112
+ </React.Fragment>
113
+ );
114
+ })}
115
+ </View>
116
+ );
117
+ };