@umituz/react-native-settings 4.17.21 → 4.17.23
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/package.json +1 -1
- package/src/domain/repositories/ISettingsRepository.ts +1 -0
- package/src/domains/appearance/presentation/components/AppearanceSection.tsx +2 -2
- package/src/domains/disclaimer/index.ts +29 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerCard.test.tsx +208 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerCard.tsx +115 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerModal.test.tsx +236 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +104 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerSetting.test.tsx +74 -0
- package/src/domains/disclaimer/presentation/components/DisclaimerSetting.tsx +108 -0
- package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +121 -0
- package/src/index.ts +3 -0
- package/src/infrastructure/storage/SettingsStore.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.17.
|
|
3
|
+
"version": "4.17.23",
|
|
4
4
|
"description": "Complete settings hub for React Native apps - consolidated package with settings, about, legal, appearance, feedback, FAQs, and rating",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -73,7 +73,7 @@ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
|
|
|
73
73
|
{ backgroundColor: `${colors.primary}15` },
|
|
74
74
|
]}
|
|
75
75
|
>
|
|
76
|
-
<AtomicIcon name="
|
|
76
|
+
<AtomicIcon name="water-outline" size="lg" color="primary" />
|
|
77
77
|
</View>
|
|
78
78
|
<View style={styles.textContainer}>
|
|
79
79
|
<AtomicText
|
|
@@ -94,7 +94,7 @@ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
|
|
|
94
94
|
</AtomicText>
|
|
95
95
|
)}
|
|
96
96
|
</View>
|
|
97
|
-
<AtomicIcon name="chevron-
|
|
97
|
+
<AtomicIcon name="chevron-forward-outline" size="md" color="secondary" />
|
|
98
98
|
</View>
|
|
99
99
|
</Pressable>
|
|
100
100
|
</View>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @umituz/react-native-disclaimer - Public API
|
|
3
|
+
*
|
|
4
|
+
* Disclaimer component for React Native apps
|
|
5
|
+
* Display legal notices, warnings, and important information
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { DisclaimerSetting, DisclaimerScreen } from '@umituz/react-native-disclaimer';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// PRESENTATION LAYER - Components
|
|
13
|
+
// =============================================================================
|
|
14
|
+
|
|
15
|
+
export { DisclaimerSetting } from './presentation/components/DisclaimerSetting';
|
|
16
|
+
export type { DisclaimerSettingProps } from './presentation/components/DisclaimerSetting';
|
|
17
|
+
|
|
18
|
+
export { DisclaimerCard } from './presentation/components/DisclaimerCard';
|
|
19
|
+
export type { DisclaimerCardProps } from './presentation/components/DisclaimerCard';
|
|
20
|
+
|
|
21
|
+
export { DisclaimerModal } from './presentation/components/DisclaimerModal';
|
|
22
|
+
export type { DisclaimerModalProps } from './presentation/components/DisclaimerModal';
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// PRESENTATION LAYER - Screens
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export { DisclaimerScreen } from './presentation/screens/DisclaimerScreen';
|
|
29
|
+
export type { DisclaimerScreenProps } from './presentation/screens/DisclaimerScreen';
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DisclaimerCard Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { DisclaimerCard } from '../DisclaimerCard';
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
jest.mock('@umituz/react-native-design-system', () => ({
|
|
11
|
+
useAppDesignTokens: () => ({
|
|
12
|
+
colors: {
|
|
13
|
+
backgroundPrimary: '#ffffff',
|
|
14
|
+
},
|
|
15
|
+
spacing: {
|
|
16
|
+
md: 16,
|
|
17
|
+
},
|
|
18
|
+
typography: {
|
|
19
|
+
labelLarge: {
|
|
20
|
+
fontWeight: '500',
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
withAlpha: jest.fn((color: string, alpha: number) => `${color}${alpha}`),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
jest.mock('@umituz/react-native-design-system', () => ({
|
|
29
|
+
AtomicText: ({ children, type, color, style }: any) => (
|
|
30
|
+
<Text style={style} testID={`atomic-text-${type}-${color}`}>
|
|
31
|
+
{children}
|
|
32
|
+
</Text>
|
|
33
|
+
),
|
|
34
|
+
AtomicIcon: ({ name, color, size, style }: any) => (
|
|
35
|
+
<Text style={style} testID={`atomic-icon-${name}-${color}-${size}`}>
|
|
36
|
+
Icon: {name}
|
|
37
|
+
</Text>
|
|
38
|
+
),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
import { Text } from 'react-native';
|
|
42
|
+
|
|
43
|
+
describe('DisclaimerCard', () => {
|
|
44
|
+
it('renders correctly with basic props', () => {
|
|
45
|
+
const { getByTestId, getByText } = render(
|
|
46
|
+
<DisclaimerCard
|
|
47
|
+
title="Test Title"
|
|
48
|
+
shortMessage="Test Message"
|
|
49
|
+
iconName="AlertTriangle"
|
|
50
|
+
iconColor="#FF9800"
|
|
51
|
+
backgroundColor="#FFF3E0"
|
|
52
|
+
onPress={jest.fn()}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
expect(getByText('Test Title')).toBeTruthy();
|
|
57
|
+
expect(getByText('Test Message')).toBeTruthy();
|
|
58
|
+
expect(getByTestId('disclaimer-setting')).toBeTruthy();
|
|
59
|
+
expect(getByTestId('atomic-icon-AlertTriangle-warning')).toBeTruthy();
|
|
60
|
+
expect(getByTestId('atomic-icon-ArrowRight-secondary-sm')).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('calls onPress when pressed', () => {
|
|
64
|
+
const mockOnPress = jest.fn();
|
|
65
|
+
const { getByTestId } = render(
|
|
66
|
+
<DisclaimerCard
|
|
67
|
+
title="Test Title"
|
|
68
|
+
shortMessage="Test Message"
|
|
69
|
+
iconName="AlertTriangle"
|
|
70
|
+
iconColor="#FF9800"
|
|
71
|
+
backgroundColor="#FFF3E0"
|
|
72
|
+
onPress={mockOnPress}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
fireEvent.press(getByTestId('disclaimer-setting'));
|
|
77
|
+
expect(mockOnPress).toHaveBeenCalledTimes(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('applies correct styling with theme tokens', () => {
|
|
81
|
+
const { getByTestId } = render(
|
|
82
|
+
<DisclaimerCard
|
|
83
|
+
title="Test Title"
|
|
84
|
+
shortMessage="Test Message"
|
|
85
|
+
iconName="AlertTriangle"
|
|
86
|
+
iconColor="#FF9800"
|
|
87
|
+
backgroundColor="#FFF3E0"
|
|
88
|
+
onPress={jest.fn()}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const card = getByTestId('disclaimer-setting');
|
|
93
|
+
expect(card.props.style).toContainEqual({
|
|
94
|
+
backgroundColor: '#FFF3E0',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders with different icon names', () => {
|
|
99
|
+
const { getByTestId } = render(
|
|
100
|
+
<DisclaimerCard
|
|
101
|
+
title="Test Title"
|
|
102
|
+
shortMessage="Test Message"
|
|
103
|
+
iconName="info"
|
|
104
|
+
iconColor="#2196F3"
|
|
105
|
+
backgroundColor="#E3F2FD"
|
|
106
|
+
onPress={jest.fn()}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(getByTestId('atomic-icon-info-warning')).toBeTruthy();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('handles long text content', () => {
|
|
114
|
+
const longTitle = 'This is a very long disclaimer title that should be handled properly';
|
|
115
|
+
const longMessage = 'This is a very long disclaimer message that should wrap properly and not break the layout';
|
|
116
|
+
|
|
117
|
+
const { getByText } = render(
|
|
118
|
+
<DisclaimerCard
|
|
119
|
+
title={longTitle}
|
|
120
|
+
shortMessage={longMessage}
|
|
121
|
+
iconName="AlertTriangle"
|
|
122
|
+
iconColor="#FF9800"
|
|
123
|
+
backgroundColor="#FFF3E0"
|
|
124
|
+
onPress={jest.fn()}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(getByText(longTitle)).toBeTruthy();
|
|
129
|
+
expect(getByText(longMessage)).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('has correct accessibility properties', () => {
|
|
133
|
+
const mockOnPress = jest.fn();
|
|
134
|
+
const { getByTestId } = render(
|
|
135
|
+
<DisclaimerCard
|
|
136
|
+
title="Test Title"
|
|
137
|
+
shortMessage="Test Message"
|
|
138
|
+
iconName="AlertTriangle"
|
|
139
|
+
iconColor="#FF9800"
|
|
140
|
+
backgroundColor="#FFF3E0"
|
|
141
|
+
onPress={mockOnPress}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const touchable = getByTestId('disclaimer-setting');
|
|
146
|
+
expect(touchable.props.activeOpacity).toBe(0.7);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('uses withAlpha utility correctly', () => {
|
|
150
|
+
const { withAlpha } = require('@umituz/react-native-design-system');
|
|
151
|
+
|
|
152
|
+
render(
|
|
153
|
+
<DisclaimerCard
|
|
154
|
+
title="Test Title"
|
|
155
|
+
shortMessage="Test Message"
|
|
156
|
+
iconName="AlertTriangle"
|
|
157
|
+
iconColor="#FF9800"
|
|
158
|
+
backgroundColor="#FFF3E0"
|
|
159
|
+
onPress={jest.fn()}
|
|
160
|
+
/>
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(withAlpha).toHaveBeenCalledWith('#FF9800', 0.2);
|
|
164
|
+
expect(withAlpha).toHaveBeenCalledWith('#FF9800', 0.4);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('maintains correct layout structure', () => {
|
|
168
|
+
const { getByTestId } = render(
|
|
169
|
+
<DisclaimerCard
|
|
170
|
+
title="Test Title"
|
|
171
|
+
shortMessage="Test Message"
|
|
172
|
+
iconName="AlertTriangle"
|
|
173
|
+
iconColor="#FF9800"
|
|
174
|
+
backgroundColor="#FFF3E0"
|
|
175
|
+
onPress={jest.fn()}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Check that icon container has correct styles
|
|
180
|
+
expect(getByTestId('atomic-icon-AlertTriangle-warning')).toBeTruthy();
|
|
181
|
+
|
|
182
|
+
// Check that title and arrow are present
|
|
183
|
+
expect(getByTestId('atomic-icon-ArrowRight-secondary-sm')).toBeTruthy();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('handles press events correctly', () => {
|
|
187
|
+
const mockOnPress = jest.fn();
|
|
188
|
+
const { getByTestId } = render(
|
|
189
|
+
<DisclaimerCard
|
|
190
|
+
title="Test Title"
|
|
191
|
+
shortMessage="Test Message"
|
|
192
|
+
iconName="AlertTriangle"
|
|
193
|
+
iconColor="#FF9800"
|
|
194
|
+
backgroundColor="#FFF3E0"
|
|
195
|
+
onPress={mockOnPress}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// Test single press
|
|
200
|
+
fireEvent.press(getByTestId('disclaimer-setting'));
|
|
201
|
+
expect(mockOnPress).toHaveBeenCalledTimes(1);
|
|
202
|
+
|
|
203
|
+
// Test multiple presses
|
|
204
|
+
fireEvent.press(getByTestId('disclaimer-setting'));
|
|
205
|
+
fireEvent.press(getByTestId('disclaimer-setting'));
|
|
206
|
+
expect(mockOnPress).toHaveBeenCalledTimes(3);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disclaimer Card Component
|
|
3
|
+
* Extracted from DisclaimerSetting to follow single responsibility and 200-line rules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
} from 'react-native';
|
|
12
|
+
|
|
13
|
+
import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system';
|
|
14
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
15
|
+
|
|
16
|
+
export interface DisclaimerCardProps {
|
|
17
|
+
title: string;
|
|
18
|
+
shortMessage: string;
|
|
19
|
+
iconName: string;
|
|
20
|
+
iconColor: string;
|
|
21
|
+
backgroundColor: string;
|
|
22
|
+
onPress: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const DisclaimerCard: React.FC<DisclaimerCardProps> = ({
|
|
26
|
+
title,
|
|
27
|
+
shortMessage,
|
|
28
|
+
iconName,
|
|
29
|
+
iconColor,
|
|
30
|
+
backgroundColor,
|
|
31
|
+
onPress,
|
|
32
|
+
}) => {
|
|
33
|
+
const tokens = useAppDesignTokens();
|
|
34
|
+
const styles = getStyles(tokens);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<TouchableOpacity
|
|
38
|
+
style={[
|
|
39
|
+
styles.container,
|
|
40
|
+
{ backgroundColor },
|
|
41
|
+
]}
|
|
42
|
+
onPress={onPress}
|
|
43
|
+
activeOpacity={0.7}
|
|
44
|
+
testID="disclaimer-setting"
|
|
45
|
+
>
|
|
46
|
+
{/* Icon and Title Row */}
|
|
47
|
+
<View style={styles.headerRow}>
|
|
48
|
+
<View
|
|
49
|
+
style={[
|
|
50
|
+
styles.iconContainer,
|
|
51
|
+
{
|
|
52
|
+
backgroundColor: withAlpha(iconColor, 0.2),
|
|
53
|
+
borderColor: withAlpha(iconColor, 0.4),
|
|
54
|
+
borderWidth: 1,
|
|
55
|
+
},
|
|
56
|
+
]}
|
|
57
|
+
>
|
|
58
|
+
<AtomicIcon name={iconName as any} color="warning" />
|
|
59
|
+
</View>
|
|
60
|
+
<AtomicText type="bodyLarge" color="primary" style={styles.title}>
|
|
61
|
+
{title}
|
|
62
|
+
</AtomicText>
|
|
63
|
+
<AtomicIcon name="arrow-right" color="secondary" size="sm" />
|
|
64
|
+
</View>
|
|
65
|
+
|
|
66
|
+
{/* Short Message */}
|
|
67
|
+
<AtomicText
|
|
68
|
+
type="bodySmall"
|
|
69
|
+
color="secondary"
|
|
70
|
+
style={styles.shortMessage}
|
|
71
|
+
>
|
|
72
|
+
{shortMessage}
|
|
73
|
+
</AtomicText>
|
|
74
|
+
</TouchableOpacity>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
79
|
+
StyleSheet.create({
|
|
80
|
+
container: {
|
|
81
|
+
paddingHorizontal: tokens.spacing.md,
|
|
82
|
+
paddingVertical: tokens.spacing.md,
|
|
83
|
+
marginHorizontal: tokens.spacing.md,
|
|
84
|
+
marginTop: 8,
|
|
85
|
+
marginBottom: 8,
|
|
86
|
+
borderRadius: 12,
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
headerRow: {
|
|
90
|
+
flexDirection: 'row',
|
|
91
|
+
alignItems: 'center',
|
|
92
|
+
marginBottom: 12,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
iconContainer: {
|
|
96
|
+
width: 40,
|
|
97
|
+
height: 40,
|
|
98
|
+
borderRadius: 20,
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
justifyContent: 'center',
|
|
101
|
+
marginRight: 12,
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
title: {
|
|
105
|
+
flex: 1,
|
|
106
|
+
fontWeight: tokens.typography.labelLarge.fontWeight as any,
|
|
107
|
+
fontSize: tokens.typography.labelLarge.fontSize,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
shortMessage: {
|
|
111
|
+
lineHeight: 18,
|
|
112
|
+
paddingLeft: 52, // Align with title (40px icon + 12px margin)
|
|
113
|
+
fontSize: 13,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DisclaimerModal Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { DisclaimerModal } from '../DisclaimerModal';
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
jest.mock('@umituz/react-native-design-system', () => ({
|
|
11
|
+
useAppDesignTokens: () => ({
|
|
12
|
+
colors: {
|
|
13
|
+
backgroundPrimary: '#ffffff',
|
|
14
|
+
borderLight: '#e0e0e0',
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock('@umituz/react-native-design-system', () => ({
|
|
20
|
+
AtomicText: ({ children, type, color, style }: any) => (
|
|
21
|
+
<Text style={style} testID={`atomic-text-${type}-${color}`}>
|
|
22
|
+
{children}
|
|
23
|
+
</Text>
|
|
24
|
+
),
|
|
25
|
+
AtomicIcon: ({ name, color, size, style }: any) => (
|
|
26
|
+
<Text style={style} testID={`atomic-icon-${name}-${color}-${size}`}>
|
|
27
|
+
Icon: {name}
|
|
28
|
+
</Text>
|
|
29
|
+
),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
import { Text, Modal, ScrollView } from 'react-native';
|
|
33
|
+
|
|
34
|
+
describe('DisclaimerModal', () => {
|
|
35
|
+
it('renders null when visible is false', () => {
|
|
36
|
+
const { queryByTestId } = render(
|
|
37
|
+
<DisclaimerModal
|
|
38
|
+
visible={false}
|
|
39
|
+
title="Test Title"
|
|
40
|
+
content="Test Content"
|
|
41
|
+
onClose={jest.fn()}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(queryByTestId('disclaimer-modal')).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('renders correctly when visible is true', () => {
|
|
49
|
+
const { getByTestId, getByText } = render(
|
|
50
|
+
<DisclaimerModal
|
|
51
|
+
visible={true}
|
|
52
|
+
title="Test Title"
|
|
53
|
+
content="Test Content"
|
|
54
|
+
onClose={jest.fn()}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(getByTestId('disclaimer-modal')).toBeTruthy();
|
|
59
|
+
expect(getByText('Test Title')).toBeTruthy();
|
|
60
|
+
expect(getByText('Test Content')).toBeTruthy();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('calls onClose when close button is pressed', () => {
|
|
64
|
+
const mockOnClose = jest.fn();
|
|
65
|
+
const { getByTestId } = render(
|
|
66
|
+
<DisclaimerModal
|
|
67
|
+
visible={true}
|
|
68
|
+
title="Test Title"
|
|
69
|
+
content="Test Content"
|
|
70
|
+
onClose={mockOnClose}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
fireEvent.press(getByTestId('close-disclaimer-modal'));
|
|
75
|
+
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('applies correct theme colors', () => {
|
|
79
|
+
const { getByTestId } = render(
|
|
80
|
+
<DisclaimerModal
|
|
81
|
+
visible={true}
|
|
82
|
+
title="Test Title"
|
|
83
|
+
content="Test Content"
|
|
84
|
+
onClose={jest.fn()}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const modalContainer = getByTestId('disclaimer-modal');
|
|
89
|
+
expect(modalContainer.props.style).toContainEqual({
|
|
90
|
+
backgroundColor: '#ffffff',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('renders header with correct structure', () => {
|
|
95
|
+
const { getByTestId, getByText } = render(
|
|
96
|
+
<DisclaimerModal
|
|
97
|
+
visible={true}
|
|
98
|
+
title="Test Title"
|
|
99
|
+
content="Test Content"
|
|
100
|
+
onClose={jest.fn()}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
|
|
105
|
+
expect(getByTestId('atomic-icon-X-primary-md')).toBeTruthy();
|
|
106
|
+
expect(getByText('Test Title')).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('renders content in ScrollView', () => {
|
|
110
|
+
const { getByTestId } = render(
|
|
111
|
+
<DisclaimerModal
|
|
112
|
+
visible={true}
|
|
113
|
+
title="Test Title"
|
|
114
|
+
content="Test Content"
|
|
115
|
+
onClose={jest.fn()}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(getByTestId('disclaimer-modal-content')).toBeTruthy();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('handles long content correctly', () => {
|
|
123
|
+
const longContent = 'This is a very long disclaimer content that should scroll properly. '.repeat(20);
|
|
124
|
+
|
|
125
|
+
const { getByText } = render(
|
|
126
|
+
<DisclaimerModal
|
|
127
|
+
visible={true}
|
|
128
|
+
title="Test Title"
|
|
129
|
+
content={longContent}
|
|
130
|
+
onClose={jest.fn()}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(getByText(longContent)).toBeTruthy();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('applies correct text styling', () => {
|
|
138
|
+
const { getByTestId } = render(
|
|
139
|
+
<DisclaimerModal
|
|
140
|
+
visible={true}
|
|
141
|
+
title="Test Title"
|
|
142
|
+
content="Test Content"
|
|
143
|
+
onClose={jest.fn()}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(getByTestId('atomic-text-bodyMedium-primary')).toBeTruthy();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('has correct accessibility properties', () => {
|
|
151
|
+
const mockOnClose = jest.fn();
|
|
152
|
+
const { getByTestId } = render(
|
|
153
|
+
<DisclaimerModal
|
|
154
|
+
visible={true}
|
|
155
|
+
title="Test Title"
|
|
156
|
+
content="Test Content"
|
|
157
|
+
onClose={mockOnClose}
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const closeButton = getByTestId('close-disclaimer-modal');
|
|
162
|
+
expect(closeButton).toBeTruthy();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('handles multiple close calls', () => {
|
|
166
|
+
const mockOnClose = jest.fn();
|
|
167
|
+
const { getByTestId } = render(
|
|
168
|
+
<DisclaimerModal
|
|
169
|
+
visible={true}
|
|
170
|
+
title="Test Title"
|
|
171
|
+
content="Test Content"
|
|
172
|
+
onClose={mockOnClose}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const closeButton = getByTestId('close-disclaimer-modal');
|
|
177
|
+
|
|
178
|
+
fireEvent.press(closeButton);
|
|
179
|
+
fireEvent.press(closeButton);
|
|
180
|
+
fireEvent.press(closeButton);
|
|
181
|
+
|
|
182
|
+
expect(mockOnClose).toHaveBeenCalledTimes(3);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('maintains correct layout structure', () => {
|
|
186
|
+
const { getByTestId } = render(
|
|
187
|
+
<DisclaimerModal
|
|
188
|
+
visible={true}
|
|
189
|
+
title="Test Title"
|
|
190
|
+
content="Test Content"
|
|
191
|
+
onClose={jest.fn()}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Check modal container exists
|
|
196
|
+
expect(getByTestId('disclaimer-modal')).toBeTruthy();
|
|
197
|
+
|
|
198
|
+
// Check header elements exist
|
|
199
|
+
expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
|
|
200
|
+
expect(getByTestId('atomic-icon-X-primary-md')).toBeTruthy();
|
|
201
|
+
|
|
202
|
+
// Check content exists
|
|
203
|
+
expect(getByTestId('disclaimer-modal-content')).toBeTruthy();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('handles empty content gracefully', () => {
|
|
207
|
+
const { getByTestId, queryByText } = render(
|
|
208
|
+
<DisclaimerModal
|
|
209
|
+
visible={true}
|
|
210
|
+
title="Test Title"
|
|
211
|
+
content=""
|
|
212
|
+
onClose={jest.fn()}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(getByTestId('disclaimer-modal')).toBeTruthy();
|
|
217
|
+
expect(getByTestId('atomic-text-headlineMedium-primary')).toBeTruthy();
|
|
218
|
+
// Empty content should still render the text component
|
|
219
|
+
expect(getByTestId('atomic-text-bodyMedium-primary')).toBeTruthy();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('handles special characters in content', () => {
|
|
223
|
+
const specialContent = 'Content with special chars: © ® ™ "quotes" \'apostrophes\'';
|
|
224
|
+
|
|
225
|
+
const { getByText } = render(
|
|
226
|
+
<DisclaimerModal
|
|
227
|
+
visible={true}
|
|
228
|
+
title="Test Title"
|
|
229
|
+
content={specialContent}
|
|
230
|
+
onClose={jest.fn()}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(getByText(specialContent)).toBeTruthy();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disclaimer Modal Component
|
|
3
|
+
* Extracted from DisclaimerSetting to follow single responsibility and 200-line rules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
TouchableOpacity,
|
|
11
|
+
ScrollView,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
|
|
14
|
+
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
15
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
16
|
+
|
|
17
|
+
export interface DisclaimerModalProps {
|
|
18
|
+
visible: boolean;
|
|
19
|
+
title: string;
|
|
20
|
+
content: string;
|
|
21
|
+
onClose: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
|
|
25
|
+
visible,
|
|
26
|
+
title,
|
|
27
|
+
content,
|
|
28
|
+
onClose,
|
|
29
|
+
}) => {
|
|
30
|
+
const tokens = useAppDesignTokens();
|
|
31
|
+
const styles = getStyles(tokens);
|
|
32
|
+
|
|
33
|
+
if (!visible) return null;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View
|
|
37
|
+
style={[
|
|
38
|
+
styles.modalContainer,
|
|
39
|
+
{ backgroundColor: tokens.colors.backgroundPrimary },
|
|
40
|
+
]}
|
|
41
|
+
>
|
|
42
|
+
{/* Modal Header */}
|
|
43
|
+
<View
|
|
44
|
+
style={[
|
|
45
|
+
styles.modalHeader,
|
|
46
|
+
{ borderBottomColor: tokens.colors.borderLight },
|
|
47
|
+
]}
|
|
48
|
+
>
|
|
49
|
+
<AtomicText type="headlineMedium" color="primary">
|
|
50
|
+
{title}
|
|
51
|
+
</AtomicText>
|
|
52
|
+
<TouchableOpacity
|
|
53
|
+
onPress={onClose}
|
|
54
|
+
testID="close-disclaimer-modal"
|
|
55
|
+
>
|
|
56
|
+
<AtomicIcon name="x" color="primary" size="md" />
|
|
57
|
+
</TouchableOpacity>
|
|
58
|
+
</View>
|
|
59
|
+
|
|
60
|
+
{/* Scrollable Content */}
|
|
61
|
+
<ScrollView
|
|
62
|
+
style={styles.modalContent}
|
|
63
|
+
contentContainerStyle={styles.modalContentContainer}
|
|
64
|
+
>
|
|
65
|
+
<AtomicText
|
|
66
|
+
type="bodyMedium"
|
|
67
|
+
color="primary"
|
|
68
|
+
style={styles.modalText}
|
|
69
|
+
>
|
|
70
|
+
{content}
|
|
71
|
+
</AtomicText>
|
|
72
|
+
</ScrollView>
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
78
|
+
StyleSheet.create({
|
|
79
|
+
modalContainer: {
|
|
80
|
+
flex: 1,
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
modalHeader: {
|
|
84
|
+
flexDirection: 'row',
|
|
85
|
+
justifyContent: 'space-between',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
paddingHorizontal: 20,
|
|
88
|
+
paddingVertical: 16,
|
|
89
|
+
borderBottomWidth: 1,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
modalContent: {
|
|
93
|
+
flex: 1,
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
modalContentContainer: {
|
|
97
|
+
padding: 20,
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
modalText: {
|
|
101
|
+
lineHeight: 24,
|
|
102
|
+
fontSize: 15,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DisclaimerSetting Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
7
|
+
import { DisclaimerSetting } from '../DisclaimerSetting';
|
|
8
|
+
|
|
9
|
+
// Mock dependencies
|
|
10
|
+
jest.mock('@umituz/react-native-localization', () => ({
|
|
11
|
+
useLocalization: () => ({
|
|
12
|
+
t: (key: string) => key,
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('@umituz/react-native-design-system', () => ({
|
|
17
|
+
useAppDesignTokens: () => ({
|
|
18
|
+
colors: {
|
|
19
|
+
backgroundPrimary: '#ffffff',
|
|
20
|
+
warning: '#ff9800',
|
|
21
|
+
},
|
|
22
|
+
spacing: {
|
|
23
|
+
md: 16,
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
withAlpha: (color: string, alpha: number) => `${color}${alpha}`,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
describe('DisclaimerSetting', () => {
|
|
30
|
+
it('renders disclaimer card correctly', () => {
|
|
31
|
+
const { getByTestId } = render(<DisclaimerSetting />);
|
|
32
|
+
|
|
33
|
+
expect(getByTestId('disclaimer-setting')).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('opens modal when card is pressed', () => {
|
|
37
|
+
const { getByTestId } = render(<DisclaimerSetting />);
|
|
38
|
+
|
|
39
|
+
const card = getByTestId('disclaimer-setting');
|
|
40
|
+
fireEvent.press(card);
|
|
41
|
+
|
|
42
|
+
// Modal should be visible now
|
|
43
|
+
expect(getByTestId('close-disclaimer-modal')).toBeTruthy();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('closes modal when close button is pressed', () => {
|
|
47
|
+
const { getByTestId } = render(<DisclaimerSetting />);
|
|
48
|
+
|
|
49
|
+
// Open modal first
|
|
50
|
+
const card = getByTestId('disclaimer-setting');
|
|
51
|
+
fireEvent.press(card);
|
|
52
|
+
|
|
53
|
+
// Close modal
|
|
54
|
+
const closeButton = getByTestId('close-disclaimer-modal');
|
|
55
|
+
fireEvent.press(closeButton);
|
|
56
|
+
|
|
57
|
+
// Modal should be closed
|
|
58
|
+
expect(() => getByTestId('close-disclaimer-modal')).toThrow();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('uses custom props when provided', () => {
|
|
62
|
+
const customProps = {
|
|
63
|
+
titleKey: 'custom.title',
|
|
64
|
+
messageKey: 'custom.message',
|
|
65
|
+
shortMessageKey: 'custom.shortMessage',
|
|
66
|
+
iconName: 'info',
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const { getByText } = render(<DisclaimerSetting {...customProps} />);
|
|
70
|
+
|
|
71
|
+
expect(getByText('custom.title')).toBeTruthy();
|
|
72
|
+
expect(getByText('custom.shortMessage')).toBeTruthy();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DisclaimerSetting Component
|
|
3
|
+
*
|
|
4
|
+
* Displays customizable disclaimer with important legal notice
|
|
5
|
+
* Used in About screens for apps that require disclaimers
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Tappable card that opens full disclaimer modal
|
|
9
|
+
* - Warning icon with background color
|
|
10
|
+
* - Internationalized title and message
|
|
11
|
+
* - Full-screen modal with scrollable content
|
|
12
|
+
* - NO shadows (CLAUDE.md compliance)
|
|
13
|
+
* - Universal across iOS, Android, Web (NO Platform.OS checks)
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* - Import and use in AboutScreen
|
|
17
|
+
* - Requires translations: settings.disclaimer.title, settings.disclaimer.message, settings.disclaimer.shortMessage
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
21
|
+
import { Modal } from 'react-native';
|
|
22
|
+
|
|
23
|
+
import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system';
|
|
24
|
+
import { useLocalization } from '@umituz/react-native-localization';
|
|
25
|
+
import { DisclaimerCard } from './DisclaimerCard';
|
|
26
|
+
import { DisclaimerModal } from './DisclaimerModal';
|
|
27
|
+
|
|
28
|
+
export interface DisclaimerSettingProps {
|
|
29
|
+
/** Custom title translation key */
|
|
30
|
+
titleKey?: string;
|
|
31
|
+
/** Custom message translation key */
|
|
32
|
+
messageKey?: string;
|
|
33
|
+
/** Custom short message translation key */
|
|
34
|
+
shortMessageKey?: string;
|
|
35
|
+
/** Custom icon name */
|
|
36
|
+
iconName?: string;
|
|
37
|
+
/** Custom icon color */
|
|
38
|
+
iconColor?: string;
|
|
39
|
+
/** Custom background color */
|
|
40
|
+
backgroundColor?: string;
|
|
41
|
+
/** Custom modal title */
|
|
42
|
+
modalTitle?: string;
|
|
43
|
+
/** Custom modal content */
|
|
44
|
+
modalContent?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const DisclaimerSetting: React.FC<DisclaimerSettingProps> = ({
|
|
48
|
+
titleKey = "settings.disclaimer.title",
|
|
49
|
+
messageKey = "settings.disclaimer.message",
|
|
50
|
+
shortMessageKey = "settings.disclaimer.shortMessage",
|
|
51
|
+
iconName = "alert-triangle",
|
|
52
|
+
iconColor,
|
|
53
|
+
backgroundColor,
|
|
54
|
+
modalTitle,
|
|
55
|
+
modalContent,
|
|
56
|
+
}) => {
|
|
57
|
+
const { t } = useLocalization();
|
|
58
|
+
const tokens = useAppDesignTokens();
|
|
59
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
return () => {
|
|
63
|
+
setModalVisible(false);
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const title = modalTitle || t(titleKey);
|
|
68
|
+
const content = modalContent || t(messageKey);
|
|
69
|
+
const shortMessage = t(shortMessageKey);
|
|
70
|
+
const finalIconColor = iconColor || tokens.colors.warning;
|
|
71
|
+
const finalBackgroundColor = backgroundColor || withAlpha(finalIconColor, 0.1);
|
|
72
|
+
|
|
73
|
+
const handleOpenModal = useCallback(() => {
|
|
74
|
+
setModalVisible(true);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const handleCloseModal = useCallback(() => {
|
|
78
|
+
setModalVisible(false);
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
<DisclaimerCard
|
|
84
|
+
title={title}
|
|
85
|
+
shortMessage={shortMessage}
|
|
86
|
+
iconName={iconName}
|
|
87
|
+
iconColor={finalIconColor}
|
|
88
|
+
backgroundColor={finalBackgroundColor}
|
|
89
|
+
onPress={handleOpenModal}
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<Modal
|
|
93
|
+
visible={modalVisible}
|
|
94
|
+
animationType="slide"
|
|
95
|
+
presentationStyle="pageSheet"
|
|
96
|
+
onRequestClose={handleCloseModal}
|
|
97
|
+
>
|
|
98
|
+
<DisclaimerModal
|
|
99
|
+
visible={modalVisible}
|
|
100
|
+
title={title}
|
|
101
|
+
content={content}
|
|
102
|
+
onClose={handleCloseModal}
|
|
103
|
+
/>
|
|
104
|
+
</Modal>
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DisclaimerScreen Component
|
|
3
|
+
*
|
|
4
|
+
* Full-screen disclaimer display for navigation-based usage
|
|
5
|
+
* Can be registered as a screen in navigation stack
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - SafeAreaView wrapper for proper display
|
|
9
|
+
* - Scrollable content for long disclaimers
|
|
10
|
+
* - Customizable title and content via props or translations
|
|
11
|
+
* - NO shadows (CLAUDE.md compliance)
|
|
12
|
+
* - Universal across iOS, Android, Web
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { View, StyleSheet, ScrollView } from 'react-native';
|
|
17
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
18
|
+
|
|
19
|
+
import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system';
|
|
20
|
+
import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
21
|
+
import { useLocalization } from '@umituz/react-native-localization';
|
|
22
|
+
|
|
23
|
+
export interface DisclaimerScreenProps {
|
|
24
|
+
/** Custom title (overrides translation) */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Custom title translation key */
|
|
27
|
+
titleKey?: string;
|
|
28
|
+
/** Custom content (overrides translation) */
|
|
29
|
+
content?: string;
|
|
30
|
+
/** Custom content translation key */
|
|
31
|
+
contentKey?: string;
|
|
32
|
+
/** Custom icon name */
|
|
33
|
+
iconName?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DisclaimerScreen: React.FC<DisclaimerScreenProps> = ({
|
|
37
|
+
title,
|
|
38
|
+
titleKey = 'settings.disclaimer.title',
|
|
39
|
+
content,
|
|
40
|
+
contentKey = 'settings.disclaimer.message',
|
|
41
|
+
iconName = 'alert-triangle',
|
|
42
|
+
}) => {
|
|
43
|
+
const { t } = useLocalization();
|
|
44
|
+
const tokens = useAppDesignTokens();
|
|
45
|
+
const styles = getStyles(tokens);
|
|
46
|
+
|
|
47
|
+
const displayTitle = title || t(titleKey);
|
|
48
|
+
const displayContent = content || t(contentKey);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<SafeAreaView
|
|
52
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
53
|
+
edges={['bottom']}
|
|
54
|
+
>
|
|
55
|
+
<ScrollView
|
|
56
|
+
style={styles.scrollView}
|
|
57
|
+
contentContainerStyle={styles.scrollContent}
|
|
58
|
+
showsVerticalScrollIndicator={false}
|
|
59
|
+
>
|
|
60
|
+
{/* Icon Header */}
|
|
61
|
+
<View style={styles.iconHeader}>
|
|
62
|
+
<View
|
|
63
|
+
style={[
|
|
64
|
+
styles.iconContainer,
|
|
65
|
+
{
|
|
66
|
+
backgroundColor: withAlpha(tokens.colors.warning, 0.1),
|
|
67
|
+
borderColor: withAlpha(tokens.colors.warning, 0.3),
|
|
68
|
+
},
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<AtomicIcon name={iconName as any} color="warning" size="lg" />
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
|
|
75
|
+
{/* Title */}
|
|
76
|
+
<AtomicText type="headlineMedium" color="primary" style={styles.title}>
|
|
77
|
+
{displayTitle}
|
|
78
|
+
</AtomicText>
|
|
79
|
+
|
|
80
|
+
{/* Content */}
|
|
81
|
+
<AtomicText type="bodyMedium" color="secondary" style={styles.content}>
|
|
82
|
+
{displayContent}
|
|
83
|
+
</AtomicText>
|
|
84
|
+
</ScrollView>
|
|
85
|
+
</SafeAreaView>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
|
|
90
|
+
StyleSheet.create({
|
|
91
|
+
container: {
|
|
92
|
+
flex: 1,
|
|
93
|
+
},
|
|
94
|
+
scrollView: {
|
|
95
|
+
flex: 1,
|
|
96
|
+
},
|
|
97
|
+
scrollContent: {
|
|
98
|
+
padding: tokens.spacing.lg,
|
|
99
|
+
paddingTop: tokens.spacing.xl,
|
|
100
|
+
},
|
|
101
|
+
iconHeader: {
|
|
102
|
+
alignItems: 'center',
|
|
103
|
+
marginBottom: tokens.spacing.lg,
|
|
104
|
+
},
|
|
105
|
+
iconContainer: {
|
|
106
|
+
width: 72,
|
|
107
|
+
height: 72,
|
|
108
|
+
borderRadius: 36,
|
|
109
|
+
alignItems: 'center',
|
|
110
|
+
justifyContent: 'center',
|
|
111
|
+
borderWidth: 2,
|
|
112
|
+
},
|
|
113
|
+
title: {
|
|
114
|
+
textAlign: 'center',
|
|
115
|
+
marginBottom: tokens.spacing.lg,
|
|
116
|
+
},
|
|
117
|
+
content: {
|
|
118
|
+
lineHeight: 24,
|
|
119
|
+
textAlign: 'left',
|
|
120
|
+
},
|
|
121
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -90,6 +90,9 @@ export * from './domains/about';
|
|
|
90
90
|
// Legal Domain - Terms, privacy, licenses
|
|
91
91
|
export * from './domains/legal';
|
|
92
92
|
|
|
93
|
+
// Disclaimer Domain - Disclaimer card, modal, settings
|
|
94
|
+
export * from './domains/disclaimer';
|
|
95
|
+
|
|
93
96
|
// Appearance Domain - Theme, dark mode
|
|
94
97
|
export * from './domains/appearance';
|
|
95
98
|
|