@umituz/react-native-settings 1.4.1 → 1.5.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Settings management for React Native apps - user preferences, theme, language, notifications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -27,7 +27,7 @@ import {
27
27
  } from 'react-native';
28
28
 
29
29
  import { useAppDesignTokens, withAlpha } from '@umituz/react-native-design-system-theme';
30
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
30
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
31
31
  import { useLocalization } from '@umituz/react-native-localization';
32
32
 
33
33
  type DesignTokens = ReturnType<typeof useAppDesignTokens>;
@@ -12,12 +12,12 @@
12
12
  */
13
13
 
14
14
  import React from 'react';
15
- import { View, StyleSheet, TouchableOpacity } from 'react-native';
15
+ import { View, StyleSheet, Pressable } from 'react-native';
16
16
  import { LinearGradient } from 'expo-linear-gradient';
17
- import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
17
+ import { AtomicText, AtomicIcon } from '@umituz/react-native-design-system-atoms';
18
18
  import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
19
- import type { IconName } from '@umituz/react-native-design-system';
20
- import type { DesignTokens } from '@umituz/react-native-design-system';
19
+ import type { IconName } from '@umituz/react-native-design-system-atoms';
20
+ import type { DesignTokens } from '@umituz/react-native-design-system-theme';
21
21
 
22
22
  interface SettingItemProps {
23
23
  /** Icon name from Lucide library */
@@ -62,86 +62,9 @@ export const SettingItem: React.FC<SettingItemProps> = ({
62
62
  ? (iconGradient as unknown as readonly [string, string, ...string[]])
63
63
  : [tokens.colors.surface, tokens.colors.surface] as const;
64
64
 
65
- if (onPress) {
66
- return (
67
- <TouchableOpacity
68
- onPress={onPress}
69
- disabled={disabled}
70
- activeOpacity={0.7}
71
- style={[styles.listItem, disabled && styles.disabled]}
72
- testID={testID}
73
- >
74
- {/* Left: Gradient Icon */}
75
- <View style={styles.leftContainer}>
76
- <LinearGradient
77
- colors={gradientColors}
78
- start={{ x: 0, y: 0 }}
79
- end={{ x: 1, y: 1 }}
80
- style={styles.iconContainer}
81
- >
82
- <AtomicIcon name={icon} size="md" customColor="#FFFFFF" />
83
- </LinearGradient>
84
- </View>
85
-
86
- {/* Center: Title and Description */}
87
- <View style={styles.contentContainer}>
88
- <AtomicText
89
- type="bodyLarge"
90
- color={disabled ? "textDisabled" : "onSurface"}
91
- style={styles.title}
92
- numberOfLines={2}
93
- ellipsizeMode="tail"
94
- >
95
- {title}
96
- </AtomicText>
97
- {description && (
98
- <AtomicText
99
- type="bodySmall"
100
- color="textSecondary"
101
- style={styles.description}
102
- numberOfLines={2}
103
- ellipsizeMode="tail"
104
- >
105
- {description}
106
- </AtomicText>
107
- )}
108
- </View>
109
-
110
- {/* Right: Value, Chevron, or Custom Element */}
111
- {rightElement ? (
112
- <View style={styles.rightContainer}>{rightElement}</View>
113
- ) : value ? (
114
- <View style={styles.rightContainer}>
115
- <AtomicText
116
- type="bodyMedium"
117
- color="textSecondary"
118
- style={styles.value}
119
- numberOfLines={2}
120
- textAlign="right"
121
- >
122
- {value}
123
- </AtomicText>
124
- </View>
125
- ) : (showChevron ?? true) ? (
126
- <View style={styles.rightContainer}>
127
- <AtomicIcon
128
- name="ChevronRight"
129
- size="sm"
130
- color="textSecondary"
131
- style={styles.chevron}
132
- />
133
- </View>
134
- ) : null}
135
- </TouchableOpacity>
136
- );
137
- }
138
-
139
- return (
140
- <View
141
- style={[styles.listItem, disabled && styles.disabled]}
142
- testID={testID}
143
- >
144
- {/* Left: Gradient Icon */}
65
+ const content = (
66
+ <View style={[styles.listItem, disabled && styles.disabled]}>
67
+ {/* Left: Icon with gradient */}
145
68
  <View style={styles.leftContainer}>
146
69
  <LinearGradient
147
70
  colors={gradientColors}
@@ -149,111 +72,102 @@ export const SettingItem: React.FC<SettingItemProps> = ({
149
72
  end={{ x: 1, y: 1 }}
150
73
  style={styles.iconContainer}
151
74
  >
152
- <AtomicIcon name={icon} size="md" customColor="#FFFFFF" />
75
+ <AtomicIcon name={icon} size="md" color="primary" />
153
76
  </LinearGradient>
154
77
  </View>
155
78
 
156
- {/* Center: Title and Description */}
79
+ {/* Center: Title and description */}
157
80
  <View style={styles.contentContainer}>
158
- <AtomicText
159
- type="bodyLarge"
160
- color={disabled ? "textDisabled" : "onSurface"}
161
- style={styles.title}
162
- numberOfLines={2}
163
- ellipsizeMode="tail"
164
- >
81
+ <AtomicText type="bodyLarge" color="textPrimary" style={styles.title} numberOfLines={2}>
165
82
  {title}
166
83
  </AtomicText>
167
84
  {description && (
168
- <AtomicText
169
- type="bodySmall"
170
- color="textSecondary"
171
- style={styles.description}
172
- numberOfLines={2}
173
- ellipsizeMode="tail"
174
- >
85
+ <AtomicText type="bodySmall" color="textSecondary" style={styles.description} numberOfLines={2}>
175
86
  {description}
176
87
  </AtomicText>
177
88
  )}
178
89
  </View>
179
90
 
180
- {/* Right: Value, Chevron, or Custom Element */}
181
- {rightElement ? (
182
- <View style={styles.rightContainer}>{rightElement}</View>
183
- ) : value ? (
184
- <View style={styles.rightContainer}>
185
- <AtomicText
186
- type="bodyMedium"
187
- color="textSecondary"
188
- style={styles.value}
189
- numberOfLines={2}
190
- textAlign="right"
191
- >
91
+ {/* Right: Value, chevron, or custom element */}
92
+ <View style={styles.rightContainer}>
93
+ {rightElement ? (
94
+ rightElement
95
+ ) : value ? (
96
+ <AtomicText type="bodyMedium" color="textSecondary" style={styles.value} numberOfLines={2}>
192
97
  {value}
193
98
  </AtomicText>
194
- </View>
195
- ) : null}
99
+ ) : (showChevron ?? true) && onPress ? (
100
+ <AtomicIcon name="ChevronRight" size="sm" customColor={tokens.colors.textSecondary} style={styles.chevron} />
101
+ ) : null}
102
+ </View>
196
103
  </View>
197
104
  );
105
+
106
+ if (onPress && !disabled) {
107
+ return (
108
+ <Pressable onPress={onPress} testID={testID} style={styles.pressable}>
109
+ {content}
110
+ </Pressable>
111
+ );
112
+ }
113
+
114
+ return <View testID={testID}>{content}</View>;
198
115
  };
199
116
 
200
117
  const getStyles = (tokens: DesignTokens) =>
201
118
  StyleSheet.create({
119
+ pressable: {
120
+ borderRadius: tokens.borders.radius.md,
121
+ },
202
122
  listItem: {
203
123
  flexDirection: 'row',
204
124
  alignItems: 'center',
205
- paddingVertical: tokens.spacing.md,
206
- paddingHorizontal: tokens.spacing.lg,
207
- minHeight: 72,
125
+ paddingVertical: tokens.spacing.sm,
126
+ paddingHorizontal: tokens.spacing.md,
127
+ minHeight: 64,
208
128
  },
209
129
  disabled: {
210
130
  opacity: 0.5,
211
131
  },
132
+ contentContainer: {
133
+ flex: 1,
134
+ marginLeft: tokens.spacing.md,
135
+ marginRight: tokens.spacing.md,
136
+ },
212
137
  leftContainer: {
213
138
  marginRight: tokens.spacing.md,
214
139
  justifyContent: 'center',
215
140
  },
216
141
  iconContainer: {
217
- width: 48,
218
- height: 48,
219
- borderRadius: 24,
142
+ width: 44,
143
+ height: 44,
144
+ borderRadius: 22,
220
145
  alignItems: 'center',
221
146
  justifyContent: 'center',
222
147
  overflow: 'hidden',
223
148
  borderWidth: 1,
224
- borderColor: `${tokens.colors.primary}15`,
225
- },
226
- contentContainer: {
227
- flex: 1,
228
- justifyContent: 'center',
229
- minWidth: 0,
149
+ borderColor: `${tokens.colors.primary}20`,
230
150
  },
231
151
  title: {
232
- fontSize: tokens.typography.bodyLarge.fontSize,
233
152
  fontWeight: '600',
234
- flexShrink: 1,
235
- lineHeight: tokens.typography.bodyLarge.fontSize * 1.4,
153
+ marginBottom: tokens.spacing.xs,
236
154
  },
237
155
  description: {
238
- fontSize: tokens.typography.bodySmall.fontSize,
239
- marginTop: tokens.spacing.xs / 2,
240
- opacity: 0.7,
241
- flexShrink: 1,
242
- lineHeight: tokens.typography.bodySmall.fontSize * 1.4,
156
+ marginTop: tokens.spacing.xs,
157
+ opacity: 0.8,
243
158
  },
244
159
  rightContainer: {
245
160
  justifyContent: 'center',
246
161
  alignItems: 'flex-end',
247
- marginLeft: tokens.spacing.md,
248
162
  maxWidth: '50%',
249
163
  flexShrink: 0,
250
164
  },
251
165
  value: {
252
166
  fontWeight: '500',
253
- lineHeight: tokens.typography.bodyMedium.fontSize * 1.4,
167
+ textAlign: 'right',
254
168
  },
255
169
  chevron: {
256
- opacity: 0.5,
170
+ opacity: 0.6,
257
171
  },
258
172
  });
259
173
 
@@ -14,15 +14,15 @@ import { View, StyleSheet } from 'react-native';
14
14
 
15
15
  import { useNavigation } from '@react-navigation/native';
16
16
  import { useDesignSystemTheme, useAppDesignTokens, type DesignTokens } from '@umituz/react-native-design-system-theme';
17
- import { AtomicText, ScreenLayout, SectionHeader, SectionContainer } from '@umituz/react-native-design-system';
17
+ import { AtomicText } from '@umituz/react-native-design-system-atoms';
18
+ import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
18
19
  import { useLocalization, getLanguageByCode } from '@umituz/react-native-localization';
19
20
  import { SettingItem } from '../components/SettingItem';
20
21
 
21
22
  export const AppearanceScreen: React.FC = () => {
22
23
  const { t, currentLanguage } = useLocalization();
23
24
  const navigation = useNavigation();
24
- // Only read themeMode, no toggle logic here - theme logic belongs in theme package
25
- const { themeMode } = useDesignSystemTheme();
25
+ const { themeMode, setThemeMode } = useDesignSystemTheme();
26
26
  const tokens = useAppDesignTokens();
27
27
  const styles = getStyles(tokens);
28
28
 
@@ -35,13 +35,8 @@ export const AppearanceScreen: React.FC = () => {
35
35
  };
36
36
 
37
37
  const handleThemeToggle = () => {
38
- // Theme toggle logic should be handled by app, not this package
39
- // This screen only displays current theme mode
40
- // App should provide toggle functionality via navigation params or event emitter
41
- // For now, just navigate back - app can handle toggle in its own theme management
42
- if (navigation.canGoBack()) {
43
- navigation.goBack();
44
- }
38
+ const newMode = themeMode === 'dark' ? 'light' : 'dark';
39
+ setThemeMode(newMode);
45
40
  };
46
41
 
47
42
  return (
@@ -57,8 +52,10 @@ export const AppearanceScreen: React.FC = () => {
57
52
  </View>
58
53
 
59
54
  {/* Language Section */}
60
- <SectionContainer>
61
- <SectionHeader>{t('settings.language')}</SectionHeader>
55
+ <View style={{ marginBottom: tokens.spacing.md }}>
56
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
57
+ {t('settings.language')}
58
+ </AtomicText>
62
59
  <SettingItem
63
60
  icon="Languages"
64
61
  iconGradient={((tokens.colors as any).settingGradients?.language as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
@@ -67,11 +64,13 @@ export const AppearanceScreen: React.FC = () => {
67
64
  onPress={handleLanguagePress}
68
65
  testID="language-button"
69
66
  />
70
- </SectionContainer>
67
+ </View>
71
68
 
72
69
  {/* Theme Section */}
73
- <SectionContainer>
74
- <SectionHeader>{t('settings.appearance.darkMode')}</SectionHeader>
70
+ <View style={{ marginBottom: tokens.spacing.md }}>
71
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
72
+ {t('settings.appearance.darkMode')}
73
+ </AtomicText>
75
74
  <SettingItem
76
75
  icon={themeMode === 'dark' ? 'Moon' : 'Sun'}
77
76
  iconGradient={
@@ -84,7 +83,7 @@ export const AppearanceScreen: React.FC = () => {
84
83
  onPress={handleThemeToggle}
85
84
  testID="theme-button"
86
85
  />
87
- </SectionContainer>
86
+ </View>
88
87
  </ScreenLayout>
89
88
  );
90
89
  };
@@ -94,11 +93,21 @@ const getStyles = (tokens: DesignTokens) =>
94
93
  header: {
95
94
  paddingBottom: tokens.spacing.lg,
96
95
  paddingTop: tokens.spacing.md,
96
+ paddingHorizontal: tokens.spacing.lg,
97
97
  },
98
98
  headerSubtitle: {
99
99
  marginTop: tokens.spacing.sm,
100
100
  lineHeight: 20,
101
101
  opacity: 0.8,
102
102
  },
103
+ sectionHeader: {
104
+ paddingHorizontal: tokens.spacing.lg,
105
+ paddingTop: tokens.spacing.lg,
106
+ paddingBottom: tokens.spacing.md,
107
+ textTransform: 'uppercase',
108
+ letterSpacing: 1,
109
+ fontWeight: '600',
110
+ fontSize: 12,
111
+ },
103
112
  });
104
113
 
@@ -15,8 +15,9 @@ import {
15
15
  TextInput,
16
16
  } from 'react-native';
17
17
  import { useNavigation } from '@react-navigation/native';
18
- import { useTheme, useAppDesignTokens, withAlpha, STATIC_TOKENS, type DesignTokens } from '@umituz/react-native-design-system-theme';
19
- import { AtomicIcon, AtomicText, ScreenLayout } from '@umituz/react-native-design-system';
18
+ import { useAppDesignTokens, withAlpha, STATIC_TOKENS, type DesignTokens } from '@umituz/react-native-design-system-theme';
19
+ import { AtomicIcon, AtomicText } from '@umituz/react-native-design-system-atoms';
20
+ import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
20
21
  import { useLocalization, searchLanguages, Language, LANGUAGES } from '@umituz/react-native-localization';
21
22
 
22
23
  /**
@@ -17,7 +17,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
17
17
 
18
18
  import { useNavigation, CommonActions } from '@react-navigation/native';
19
19
  import { useDesignSystemTheme, useAppDesignTokens } from '@umituz/react-native-design-system-theme';
20
- import { ScreenLayout, AtomicIcon, AtomicText, SectionHeader, SectionContainer, AtomicDivider } from '@umituz/react-native-design-system';
20
+ import { ScreenLayout } from '@umituz/react-native-design-system-organisms';
21
+ import { AtomicIcon, AtomicText } from '@umituz/react-native-design-system-atoms';
21
22
  import { SettingItem } from '../components/SettingItem';
22
23
  import { getLanguageByCode, useLocalization } from '@umituz/react-native-localization';
23
24
  import { SettingsConfig } from './types';
@@ -86,11 +87,11 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
86
87
  config = {}
87
88
  }) => {
88
89
  const navigation = useNavigation();
89
- // Only read themeMode, no theme logic here - theme logic belongs in theme package
90
90
  const { themeMode } = useDesignSystemTheme();
91
91
  const tokens = useAppDesignTokens();
92
92
  const insets = useSafeAreaInsets();
93
93
  const { currentLanguage, t } = useLocalization();
94
+ const styles = getStyles(tokens);
94
95
 
95
96
  const currentLang = getLanguageByCode(currentLanguage);
96
97
  const languageDisplay = currentLang ? `${currentLang.flag} ${currentLang.nativeName}` : 'English';
@@ -267,8 +268,10 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
267
268
 
268
269
  {/* Appearance Section */}
269
270
  {features.appearance && (
270
- <SectionContainer>
271
- <SectionHeader>{t('settings.sections.appearance')}</SectionHeader>
271
+ <View style={{ marginBottom: tokens.spacing.md }}>
272
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
273
+ {t('settings.sections.appearance')}
274
+ </AtomicText>
272
275
  <SettingItem
273
276
  icon="Palette"
274
277
  iconGradient={((tokens.colors as any).settingGradients?.themeLight as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
@@ -277,13 +280,15 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
277
280
  onPress={handleAppearancePress}
278
281
  testID="appearance-button"
279
282
  />
280
- </SectionContainer>
283
+ </View>
281
284
  )}
282
285
 
283
286
  {/* General Section - Notifications */}
284
287
  {features.notifications && (
285
- <SectionContainer>
286
- <SectionHeader>{t('settings.sections.general')}</SectionHeader>
288
+ <View style={{ marginBottom: tokens.spacing.md }}>
289
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
290
+ {t('settings.sections.general')}
291
+ </AtomicText>
287
292
  <SettingItem
288
293
  icon="Bell"
289
294
  iconGradient={((tokens.colors as any).settingGradients?.notifications as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
@@ -292,13 +297,15 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
292
297
  onPress={handleNotificationsPress}
293
298
  testID="notifications-button"
294
299
  />
295
- </SectionContainer>
300
+ </View>
296
301
  )}
297
302
 
298
303
  {/* Development/Test: Show Onboarding */}
299
304
  {__DEV__ && useOnboardingStore && (
300
- <SectionContainer>
301
- <SectionHeader>Development</SectionHeader>
305
+ <View style={{ marginBottom: tokens.spacing.md }}>
306
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
307
+ Development
308
+ </AtomicText>
302
309
  <SettingItem
303
310
  icon="Play"
304
311
  iconGradient={((tokens.colors as any).settingGradients?.info as unknown as string[]) || [tokens.colors.primary, tokens.colors.secondary]}
@@ -307,13 +314,15 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
307
314
  onPress={handleShowOnboarding}
308
315
  testID="show-onboarding-button"
309
316
  />
310
- </SectionContainer>
317
+ </View>
311
318
  )}
312
319
 
313
320
  {/* About & Legal Section */}
314
321
  {(features.about || features.legal) && (
315
- <SectionContainer>
316
- <SectionHeader>{t('settings.sections.about')}</SectionHeader>
322
+ <View style={{ marginBottom: tokens.spacing.md }}>
323
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
324
+ {t('settings.sections.about')}
325
+ </AtomicText>
317
326
  {features.about && (
318
327
  <SettingItem
319
328
  icon="Info"
@@ -324,7 +333,9 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
324
333
  testID="about-button"
325
334
  />
326
335
  )}
327
- {features.about && features.legal && <AtomicDivider />}
336
+ {features.about && features.legal && (
337
+ <View style={{ height: 1, backgroundColor: tokens.colors.borderLight, marginVertical: tokens.spacing.sm }} />
338
+ )}
328
339
  {features.legal && (
329
340
  <SettingItem
330
341
  icon="FileText"
@@ -335,35 +346,44 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
335
346
  testID="legal-button"
336
347
  />
337
348
  )}
338
- </SectionContainer>
349
+ </View>
339
350
  )}
340
351
 
341
352
  {/* Fallback: Show message if no features are enabled */}
342
353
  {!hasAnyFeatures && (
343
- <SectionContainer>
344
- <SectionHeader>
354
+ <View>
355
+ <AtomicText type="labelMedium" color="textSecondary" style={styles.sectionHeader}>
345
356
  {t('settings.noOptionsAvailable') || 'No settings available'}
346
- </SectionHeader>
347
- </SectionContainer>
357
+ </AtomicText>
358
+ </View>
348
359
  )}
349
360
  </ScreenLayout>
350
361
  );
351
362
  };
352
363
 
353
- const styles = StyleSheet.create({
364
+ const getStyles = (tokens: any) => StyleSheet.create({
354
365
  header: {
355
366
  flexDirection: 'row',
356
367
  alignItems: 'center',
357
368
  justifyContent: 'space-between',
358
- paddingHorizontal: 16,
359
- paddingBottom: 12,
360
- paddingTop: 12,
369
+ paddingHorizontal: tokens.spacing.md,
370
+ paddingBottom: tokens.spacing.md,
371
+ paddingTop: tokens.spacing.md,
361
372
  borderBottomWidth: 1,
362
373
  zIndex: 1000,
363
374
  },
375
+ sectionHeader: {
376
+ paddingHorizontal: tokens.spacing.lg,
377
+ paddingTop: tokens.spacing.lg,
378
+ paddingBottom: tokens.spacing.md,
379
+ textTransform: 'uppercase',
380
+ letterSpacing: 1,
381
+ fontWeight: '600',
382
+ fontSize: 12,
383
+ },
364
384
  closeButton: {
365
- padding: 8,
366
- marginLeft: 8,
385
+ padding: tokens.spacing.sm,
386
+ marginLeft: tokens.spacing.sm,
367
387
  },
368
388
  });
369
389