@umituz/react-native-settings 4.17.14 → 4.17.16

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 (103) hide show
  1. package/package.json +16 -15
  2. package/src/domains/about/__tests__/integration.test.tsx +328 -0
  3. package/src/domains/about/__tests__/types.d.ts +5 -0
  4. package/src/domains/about/domain/entities/AppInfo.ts +74 -0
  5. package/src/domains/about/domain/entities/__tests__/AppInfo.test.ts +93 -0
  6. package/src/domains/about/domain/repositories/IAboutRepository.ts +22 -0
  7. package/src/domains/about/index.ts +10 -0
  8. package/src/domains/about/infrastructure/repositories/AboutRepository.ts +68 -0
  9. package/src/domains/about/infrastructure/repositories/__tests__/AboutRepository.test.ts +153 -0
  10. package/src/domains/about/presentation/components/AboutContent.tsx +104 -0
  11. package/src/domains/about/presentation/components/AboutHeader.tsx +79 -0
  12. package/src/domains/about/presentation/components/AboutSection.tsx +134 -0
  13. package/src/domains/about/presentation/components/AboutSettingItem.tsx +208 -0
  14. package/src/domains/about/presentation/components/__tests__/AboutContent.simple.test.tsx +178 -0
  15. package/src/domains/about/presentation/components/__tests__/AboutContent.test.tsx +293 -0
  16. package/src/domains/about/presentation/components/__tests__/AboutHeader.test.tsx +201 -0
  17. package/src/domains/about/presentation/components/__tests__/AboutSettingItem.test.tsx +71 -0
  18. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.simple.test.tsx +229 -0
  19. package/src/domains/about/presentation/hooks/__tests__/useAboutInfo.test.tsx +240 -0
  20. package/src/domains/about/presentation/hooks/useAboutInfo.ts +262 -0
  21. package/src/domains/about/presentation/screens/AboutScreen.tsx +195 -0
  22. package/src/domains/about/presentation/screens/__tests__/AboutScreen.simple.test.tsx +199 -0
  23. package/src/domains/about/presentation/screens/__tests__/AboutScreen.test.tsx +366 -0
  24. package/src/domains/about/types/global.d.ts +15 -0
  25. package/src/domains/about/utils/__tests__/index.test.ts +408 -0
  26. package/src/domains/about/utils/index.ts +160 -0
  27. package/src/domains/appearance/__tests__/components/AppearanceScreen.test.tsx +195 -0
  28. package/src/domains/appearance/__tests__/hooks/index.test.tsx +232 -0
  29. package/src/domains/appearance/__tests__/integration/index.test.tsx +207 -0
  30. package/src/domains/appearance/__tests__/services/appearanceService.test.ts +299 -0
  31. package/src/domains/appearance/__tests__/setup.ts +96 -0
  32. package/src/domains/appearance/__tests__/stores/appearanceStore.test.tsx +175 -0
  33. package/src/domains/appearance/data/colorPalettes.ts +94 -0
  34. package/src/domains/appearance/hooks/index.ts +6 -0
  35. package/src/domains/appearance/hooks/useAppearance.ts +61 -0
  36. package/src/domains/appearance/hooks/useAppearanceActions.ts +144 -0
  37. package/src/domains/appearance/index.ts +7 -0
  38. package/src/domains/appearance/infrastructure/services/appearanceService.ts +301 -0
  39. package/src/domains/appearance/infrastructure/services/systemThemeDetection.ts +79 -0
  40. package/src/domains/appearance/infrastructure/services/validation.ts +91 -0
  41. package/src/domains/appearance/infrastructure/storage/appearanceStorage.ts +120 -0
  42. package/src/domains/appearance/infrastructure/stores/appearanceStore.ts +132 -0
  43. package/src/domains/appearance/presentation/components/AppearanceHeader.tsx +67 -0
  44. package/src/domains/appearance/presentation/components/AppearancePreview.tsx +141 -0
  45. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +139 -0
  46. package/src/domains/appearance/presentation/components/ColorPicker.tsx +113 -0
  47. package/src/domains/appearance/presentation/components/CustomColorsSection.tsx +186 -0
  48. package/src/domains/appearance/presentation/components/ThemeModeSection.tsx +110 -0
  49. package/src/domains/appearance/presentation/components/ThemeOption.tsx +138 -0
  50. package/src/domains/appearance/presentation/components/index.ts +6 -0
  51. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +226 -0
  52. package/src/domains/appearance/presentation/screens/index.ts +2 -0
  53. package/src/domains/appearance/types/index.ts +54 -0
  54. package/src/domains/faqs/domain/entities/FAQEntity.ts +16 -0
  55. package/src/domains/faqs/domain/services/FAQSearchService.ts +36 -0
  56. package/src/domains/faqs/domain/services/index.ts +1 -0
  57. package/src/domains/faqs/index.ts +7 -0
  58. package/src/domains/faqs/presentation/components/FAQCategory.tsx +71 -0
  59. package/src/domains/faqs/presentation/components/FAQEmptyState.tsx +75 -0
  60. package/src/domains/faqs/presentation/components/FAQItem.tsx +103 -0
  61. package/src/domains/faqs/presentation/components/FAQSearchBar.tsx +70 -0
  62. package/src/domains/faqs/presentation/components/FAQSection.tsx +50 -0
  63. package/src/domains/faqs/presentation/components/index.ts +18 -0
  64. package/src/domains/faqs/presentation/hooks/index.ts +6 -0
  65. package/src/domains/faqs/presentation/hooks/useFAQExpansion.ts +51 -0
  66. package/src/domains/faqs/presentation/hooks/useFAQSearch.ts +33 -0
  67. package/src/domains/faqs/presentation/screens/FAQScreen.tsx +129 -0
  68. package/src/domains/faqs/presentation/screens/index.ts +2 -0
  69. package/src/domains/feedback/domain/entities/FeedbackEntity.ts +92 -0
  70. package/src/domains/feedback/domain/repositories/IFeedbackRepository.ts +28 -0
  71. package/src/domains/feedback/index.ts +6 -0
  72. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +189 -0
  73. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +111 -0
  74. package/src/domains/feedback/presentation/components/SupportSection.tsx +160 -0
  75. package/src/domains/feedback/presentation/hooks/useDeleteFeedback.ts +25 -0
  76. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +59 -0
  77. package/src/domains/feedback/presentation/hooks/useSubmitFeedback.ts +55 -0
  78. package/src/domains/feedback/presentation/hooks/useUserFeedback.ts +29 -0
  79. package/src/domains/legal/__tests__/ContentValidationService.test.ts +195 -0
  80. package/src/domains/legal/__tests__/StyleCacheService.test.ts +110 -0
  81. package/src/domains/legal/__tests__/UrlHandlerService.test.ts +71 -0
  82. package/src/domains/legal/__tests__/setup.ts +82 -0
  83. package/src/domains/legal/domain/entities/LegalConfig.ts +26 -0
  84. package/src/domains/legal/domain/services/ContentValidationService.ts +89 -0
  85. package/src/domains/legal/domain/services/StyleCacheService.ts +97 -0
  86. package/src/domains/legal/domain/services/UrlHandlerService.ts +128 -0
  87. package/src/domains/legal/index.ts +8 -0
  88. package/src/domains/legal/presentation/components/LegalItem.tsx +177 -0
  89. package/src/domains/legal/presentation/components/LegalLinks.tsx +154 -0
  90. package/src/domains/legal/presentation/components/LegalSection.tsx +134 -0
  91. package/src/domains/legal/presentation/screens/LegalScreen.tsx +237 -0
  92. package/src/domains/legal/presentation/screens/PrivacyPolicyScreen.tsx +214 -0
  93. package/src/domains/legal/presentation/screens/TermsOfServiceScreen.tsx +214 -0
  94. package/src/index.ts +19 -0
  95. package/src/presentation/components/DevSettingsSection.tsx +2 -2
  96. package/src/presentation/components/SettingItem.tsx +2 -2
  97. package/src/presentation/components/SettingsErrorBoundary.tsx +2 -2
  98. package/src/presentation/components/SettingsFooter.tsx +2 -2
  99. package/src/presentation/components/SettingsSection.tsx +2 -2
  100. package/src/presentation/navigation/SettingsStackNavigator.tsx +2 -2
  101. package/src/presentation/screens/SettingsScreen.tsx +2 -2
  102. package/src/presentation/screens/components/SettingsContent.tsx +2 -2
  103. package/src/presentation/screens/components/SettingsHeader.tsx +2 -2
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Appearance Storage Service
3
+ *
4
+ * Handles persistence of appearance settings using AsyncStorage
5
+ * Single Responsibility: Pure storage operations only
6
+ */
7
+
8
+ import { storageRepository, unwrap } from "@umituz/react-native-storage";
9
+ import type { ThemeMode } from "@umituz/react-native-design-system";
10
+ import type { AppearanceSettings } from "../../types";
11
+
12
+ const STORAGE_KEYS = {
13
+ APPEARANCE_SETTINGS: "@appearance_settings",
14
+ } as const;
15
+
16
+ const DEFAULT_SETTINGS: AppearanceSettings = {
17
+ themeMode: "dark",
18
+ };
19
+
20
+ export class AppearanceStorage {
21
+ /**
22
+ * Get saved appearance settings
23
+ * Pure storage operation - no business logic
24
+ */
25
+ static async getSettings(): Promise<AppearanceSettings | null> {
26
+ try {
27
+ const result = await storageRepository.getItem<AppearanceSettings>(
28
+ STORAGE_KEYS.APPEARANCE_SETTINGS,
29
+ DEFAULT_SETTINGS,
30
+ );
31
+ const data = unwrap(result, DEFAULT_SETTINGS);
32
+ return data;
33
+ } catch (error) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Save appearance settings
40
+ * Pure storage operation - no business logic
41
+ */
42
+ static async setSettings(settings: AppearanceSettings): Promise<void> {
43
+ const result = await storageRepository.setItem(
44
+ STORAGE_KEYS.APPEARANCE_SETTINGS,
45
+ settings,
46
+ );
47
+ if (!result.success) {
48
+ throw new Error("Failed to save appearance settings");
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Get saved theme mode
54
+ * Pure storage operation - no business logic
55
+ */
56
+ static async getThemeMode(): Promise<ThemeMode | null> {
57
+ try {
58
+ const settings = await this.getSettings();
59
+ return settings?.themeMode || null;
60
+ } catch (error) {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Save theme mode
67
+ * Pure storage operation - no business logic
68
+ */
69
+ static async setThemeMode(themeMode: ThemeMode): Promise<void> {
70
+ const currentSettings = (await this.getSettings()) || {
71
+ themeMode: "dark",
72
+ };
73
+ await this.setSettings({
74
+ ...currentSettings,
75
+ themeMode,
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Get custom theme colors
81
+ * Pure storage operation - no business logic
82
+ */
83
+ static async getCustomColors() {
84
+ try {
85
+ const settings = await this.getSettings();
86
+ return settings?.customColors || null;
87
+ } catch (error) {
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Save custom theme colors
94
+ * Pure storage operation - no business logic
95
+ */
96
+ static async setCustomColors(
97
+ customColors: AppearanceSettings["customColors"],
98
+ ): Promise<void> {
99
+ const currentSettings = (await this.getSettings()) || {
100
+ themeMode: "dark",
101
+ };
102
+ await this.setSettings({
103
+ ...currentSettings,
104
+ customColors,
105
+ });
106
+ }
107
+
108
+ /**
109
+ * Clear all appearance settings
110
+ * Pure storage operation - no business logic
111
+ */
112
+ static async clear(): Promise<void> {
113
+ const result = await storageRepository.removeItem(
114
+ STORAGE_KEYS.APPEARANCE_SETTINGS,
115
+ );
116
+ if (!result.success) {
117
+ throw new Error("Failed to clear appearance settings");
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Appearance Store
3
+ *
4
+ * Zustand store for appearance state management
5
+ * Single Responsibility: Pure state management only
6
+ */
7
+
8
+ import { create } from "zustand";
9
+ import type { AppearanceSettings, AppearanceState } from "../../types";
10
+
11
+ interface AppearanceStoreActions {
12
+ // Pure state mutations only
13
+ setSettings: (settings: AppearanceSettings) => void;
14
+ setInitialized: (initialized: boolean) => void;
15
+ updateThemeMode: (mode: AppearanceSettings["themeMode"]) => void;
16
+ updateCustomColors: (colors: AppearanceSettings["customColors"]) => void;
17
+ resetState: () => void;
18
+ }
19
+
20
+ type AppearanceStore = AppearanceState & AppearanceStoreActions;
21
+
22
+ const DEFAULT_SETTINGS: AppearanceSettings = {
23
+ themeMode: "dark", // Use dark mode as default
24
+ };
25
+
26
+ export const useAppearanceStore = create<AppearanceStore>((set, get) => ({
27
+ settings: DEFAULT_SETTINGS,
28
+ isInitialized: false,
29
+
30
+ // Pure state mutations with performance optimizations
31
+ setSettings: (settings: AppearanceSettings) => {
32
+ // Prevent unnecessary updates if settings are the same
33
+ const currentSettings = get().settings;
34
+ if (JSON.stringify(currentSettings) === JSON.stringify(settings)) {
35
+ if (__DEV__) {
36
+ console.log("[AppearanceStore] Skipping settings update - no changes");
37
+ }
38
+ return;
39
+ }
40
+
41
+ if (__DEV__) {
42
+ console.log("[AppearanceStore] Setting appearance settings:", settings);
43
+ }
44
+ set({ settings });
45
+ },
46
+
47
+ setInitialized: (initialized: boolean) => {
48
+ // Prevent unnecessary updates if state is the same
49
+ const currentInitialized = get().isInitialized;
50
+ if (currentInitialized === initialized) {
51
+ if (__DEV__) {
52
+ console.log("[AppearanceStore] Skipping initialized update - no change");
53
+ }
54
+ return;
55
+ }
56
+
57
+ if (__DEV__) {
58
+ console.log("[AppearanceStore] Setting initialized state:", initialized);
59
+ }
60
+ set({ isInitialized: initialized });
61
+ },
62
+
63
+ updateThemeMode: (mode: AppearanceSettings["themeMode"]) => {
64
+ const currentSettings = get().settings;
65
+
66
+ // Prevent unnecessary updates if mode is the same
67
+ if (currentSettings.themeMode === mode) {
68
+ if (__DEV__) {
69
+ console.log("[AppearanceStore] Skipping theme mode update - no change");
70
+ }
71
+ return;
72
+ }
73
+
74
+ const newSettings: AppearanceSettings = {
75
+ ...currentSettings,
76
+ themeMode: mode,
77
+ };
78
+
79
+ if (__DEV__) {
80
+ console.log("[AppearanceStore] Updating theme mode:", mode);
81
+ }
82
+
83
+ set({ settings: newSettings });
84
+ },
85
+
86
+ updateCustomColors: (colors: AppearanceSettings["customColors"]) => {
87
+ const currentSettings = get().settings;
88
+
89
+ // Prevent unnecessary updates if colors are the same
90
+ if (JSON.stringify(currentSettings.customColors) === JSON.stringify(colors)) {
91
+ if (__DEV__) {
92
+ console.log("[AppearanceStore] Skipping custom colors update - no changes");
93
+ }
94
+ return;
95
+ }
96
+
97
+ const newSettings: AppearanceSettings = {
98
+ ...currentSettings,
99
+ customColors: colors,
100
+ };
101
+
102
+ if (__DEV__) {
103
+ console.log("[AppearanceStore] Updating custom colors:", colors);
104
+ }
105
+
106
+ set({ settings: newSettings });
107
+ },
108
+
109
+ resetState: () => {
110
+ const currentState = get();
111
+
112
+ // Prevent unnecessary reset if already at default
113
+ if (
114
+ currentState.isInitialized === false &&
115
+ JSON.stringify(currentState.settings) === JSON.stringify(DEFAULT_SETTINGS)
116
+ ) {
117
+ if (__DEV__) {
118
+ console.log("[AppearanceStore] Skipping reset - already at default state");
119
+ }
120
+ return;
121
+ }
122
+
123
+ if (__DEV__) {
124
+ console.log("[AppearanceStore] Resetting to default state");
125
+ }
126
+
127
+ set({
128
+ settings: DEFAULT_SETTINGS,
129
+ isInitialized: false,
130
+ });
131
+ },
132
+ }));
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Appearance Header Component
3
+ * Single Responsibility: Render appearance screen header
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicText } from "@umituz/react-native-design-system";
9
+ import type { DesignTokens } from "@umituz/react-native-design-system";
10
+
11
+ export interface AppearanceHeaderProps {
12
+ tokens: DesignTokens;
13
+ title?: string;
14
+ subtitle?: string;
15
+ titleType?: "headlineLarge" | "headlineMedium" | "headlineSmall";
16
+ subtitleType?: "bodyLarge" | "bodyMedium" | "bodySmall";
17
+ titleColor?: "primary" | "secondary" | "tertiary";
18
+ subtitleColor?: "primary" | "secondary" | "tertiary";
19
+ style?: any;
20
+ }
21
+
22
+ export const AppearanceHeader: React.FC<AppearanceHeaderProps> = ({
23
+ tokens,
24
+ title,
25
+ subtitle,
26
+ titleType = "headlineLarge",
27
+ subtitleType = "bodyMedium",
28
+ titleColor = "primary",
29
+ subtitleColor = "secondary",
30
+ style,
31
+ }) => {
32
+ // Don't render anything if there's no content
33
+ if (!title && !subtitle) {
34
+ return null;
35
+ }
36
+
37
+ const styles = getStyles(tokens);
38
+
39
+ return (
40
+ <View style={[styles.header, style]}>
41
+ {title ? (
42
+ <AtomicText type={titleType} color={titleColor}>
43
+ {title}
44
+ </AtomicText>
45
+ ) : null}
46
+ {subtitle ? (
47
+ <AtomicText
48
+ type={subtitleType}
49
+ color={subtitleColor}
50
+ style={styles.headerSubtitle}
51
+ >
52
+ {subtitle}
53
+ </AtomicText>
54
+ ) : null}
55
+ </View>
56
+ );
57
+ };
58
+
59
+ const getStyles = (tokens: DesignTokens) =>
60
+ StyleSheet.create({
61
+ header: {
62
+ marginBottom: tokens.spacing.lg,
63
+ },
64
+ headerSubtitle: {
65
+ marginTop: tokens.spacing.sm,
66
+ },
67
+ });
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Appearance Preview Component
3
+ * Single Responsibility: Render color preview section
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import { AtomicText } from "@umituz/react-native-design-system";
9
+ import type { DesignTokens } from "@umituz/react-native-design-system";
10
+ import type { CustomThemeColors } from "../../types";
11
+
12
+ export interface PreviewColorItem {
13
+ key: keyof CustomThemeColors;
14
+ label: string;
15
+ fallbackColor: string;
16
+ }
17
+
18
+ export interface AppearancePreviewProps {
19
+ tokens: DesignTokens;
20
+ localCustomColors: CustomThemeColors;
21
+ title?: string;
22
+ description?: string;
23
+ previewColors?: PreviewColorItem[];
24
+ showPreview?: boolean;
25
+ }
26
+
27
+ const DEFAULT_PREVIEW_COLORS: PreviewColorItem[] = [
28
+ {
29
+ key: "primary",
30
+ label: "Primary",
31
+ fallbackColor: "#007AFF",
32
+ },
33
+ {
34
+ key: "secondary",
35
+ label: "Secondary",
36
+ fallbackColor: "#8E8E93",
37
+ },
38
+ {
39
+ key: "accent",
40
+ label: "Accent",
41
+ fallbackColor: "#FF6B6B",
42
+ },
43
+ {
44
+ key: "buttonPrimary",
45
+ label: "Button",
46
+ fallbackColor: "#007AFF",
47
+ },
48
+ ];
49
+
50
+ export const AppearancePreview: React.FC<AppearancePreviewProps> = ({
51
+ tokens,
52
+ localCustomColors,
53
+ title,
54
+ description,
55
+ previewColors = DEFAULT_PREVIEW_COLORS,
56
+ showPreview = true,
57
+ }) => {
58
+ const styles = getStyles(tokens);
59
+
60
+ const colors = previewColors.map((item) => ({
61
+ label: item.label,
62
+ color: localCustomColors[item.key] || item.fallbackColor,
63
+ }));
64
+
65
+ if (!showPreview) {
66
+ return null;
67
+ }
68
+
69
+ return (
70
+ <View style={styles.previewSection}>
71
+ <AtomicText
72
+ type="titleMedium"
73
+ color="primary"
74
+ style={styles.sectionTitle}
75
+ >
76
+ {title}
77
+ </AtomicText>
78
+ <AtomicText
79
+ type="bodySmall"
80
+ color="secondary"
81
+ style={styles.sectionDescription}
82
+ >
83
+ {description}
84
+ </AtomicText>
85
+ <View style={styles.previewContainer}>
86
+ <View style={styles.previewColorRow}>
87
+ {colors.map((item, index) => (
88
+ <View key={index} style={styles.previewColorItem}>
89
+ <View
90
+ style={[
91
+ styles.previewColorCircle,
92
+ { backgroundColor: item.color },
93
+ ]}
94
+ />
95
+ <AtomicText type="bodySmall" color="secondary">
96
+ {item.label}
97
+ </AtomicText>
98
+ </View>
99
+ ))}
100
+ </View>
101
+ </View>
102
+ </View>
103
+ );
104
+ };
105
+
106
+ const getStyles = (tokens: DesignTokens) =>
107
+ StyleSheet.create({
108
+ previewSection: {
109
+ marginBottom: tokens.spacing.lg,
110
+ },
111
+ sectionTitle: {
112
+ marginBottom: tokens.spacing.xs,
113
+ },
114
+ sectionDescription: {
115
+ marginBottom: tokens.spacing.md,
116
+ },
117
+ previewContainer: {
118
+ backgroundColor: tokens.colors.surface,
119
+ borderRadius: 12,
120
+ padding: tokens.spacing.md,
121
+ borderLeftWidth: 4,
122
+ borderLeftColor: tokens.colors.primary,
123
+ },
124
+ previewColorRow: {
125
+ flexDirection: "row",
126
+ flexWrap: "wrap",
127
+ gap: tokens.spacing.md,
128
+ marginTop: tokens.spacing.sm,
129
+ },
130
+ previewColorItem: {
131
+ alignItems: "center",
132
+ },
133
+ previewColorCircle: {
134
+ width: 32,
135
+ height: 32,
136
+ borderRadius: 16,
137
+ marginBottom: tokens.spacing.xs,
138
+ borderWidth: 1,
139
+ borderColor: tokens.colors.border,
140
+ },
141
+ });
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import { View, Pressable, StyleSheet, ViewStyle } from 'react-native';
3
+ import { useNavigation } from '@react-navigation/native';
4
+ import { useResponsiveDesignTokens, AtomicIcon, AtomicText } from '@umituz/react-native-design-system';
5
+ import { AppearanceSectionConfig } from '../../types';
6
+
7
+ export interface AppearanceSectionProps {
8
+ config?: AppearanceSectionConfig;
9
+ onPress?: () => void;
10
+ containerStyle?: ViewStyle;
11
+ sectionTitle?: string;
12
+ /** Optional explicit title override */
13
+ title?: string;
14
+ /** Optional explicit description override */
15
+ description?: string;
16
+ }
17
+
18
+ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
19
+ config,
20
+ onPress,
21
+ containerStyle,
22
+ sectionTitle,
23
+ title: titleProp,
24
+ description: descriptionProp,
25
+ }) => {
26
+ const navigation = useNavigation();
27
+ const tokens = useResponsiveDesignTokens();
28
+ const colors = tokens.colors;
29
+
30
+ const route = config?.route || config?.defaultRoute || 'Appearance';
31
+ // Use props first, then config, then strict empty string to avoid hardcoded English
32
+ const title = titleProp || config?.title;
33
+ const description = descriptionProp || config?.description;
34
+
35
+ // Only display section title if provided
36
+ const displaySectionTitle = sectionTitle || title;
37
+
38
+ const handlePress = () => {
39
+ if (onPress) {
40
+ onPress();
41
+ } else {
42
+ navigation.navigate(route as never);
43
+ }
44
+ };
45
+
46
+ if (!title) return null;
47
+
48
+ return (
49
+ <View style={[styles.sectionContainer, { backgroundColor: colors.surface }, containerStyle]}>
50
+ {!!displaySectionTitle && (
51
+ <View style={styles.headerContainer}>
52
+ <AtomicText
53
+ type="titleMedium"
54
+ color="primary"
55
+ >
56
+ {displaySectionTitle}
57
+ </AtomicText>
58
+ </View>
59
+ )}
60
+ <Pressable
61
+ style={({ pressed }) => [
62
+ styles.itemContainer,
63
+ {
64
+ backgroundColor: pressed ? `${colors.primary}08` : 'transparent',
65
+ },
66
+ ]}
67
+ onPress={handlePress}
68
+ >
69
+ <View style={styles.content}>
70
+ <View
71
+ style={[
72
+ styles.iconContainer,
73
+ { backgroundColor: `${colors.primary}15` },
74
+ ]}
75
+ >
76
+ <AtomicIcon name="droplet" size="lg" color="primary" />
77
+ </View>
78
+ <View style={styles.textContainer}>
79
+ <AtomicText
80
+ type="bodyLarge"
81
+ color="primary"
82
+ numberOfLines={1}
83
+ style={{ marginBottom: 4 }}
84
+ >
85
+ {title}
86
+ </AtomicText>
87
+ {!!description && (
88
+ <AtomicText
89
+ type="bodyMedium"
90
+ color="secondary"
91
+ numberOfLines={2}
92
+ >
93
+ {description}
94
+ </AtomicText>
95
+ )}
96
+ </View>
97
+ <AtomicIcon name="chevron-right" size="md" color="secondary" />
98
+ </View>
99
+ </Pressable>
100
+ </View>
101
+ );
102
+ };
103
+
104
+ const styles = StyleSheet.create({
105
+ sectionContainer: {
106
+ marginBottom: 16,
107
+ borderRadius: 12,
108
+ overflow: 'hidden',
109
+ },
110
+ headerContainer: {
111
+ paddingHorizontal: 16,
112
+ paddingTop: 16,
113
+ paddingBottom: 8,
114
+ },
115
+ itemContainer: {
116
+ flexDirection: 'row',
117
+ alignItems: 'center',
118
+ paddingHorizontal: 16,
119
+ paddingVertical: 16,
120
+ minHeight: 72,
121
+ },
122
+ content: {
123
+ flex: 1,
124
+ flexDirection: 'row',
125
+ alignItems: 'center',
126
+ },
127
+ iconContainer: {
128
+ width: 48,
129
+ height: 48,
130
+ borderRadius: 12,
131
+ justifyContent: 'center',
132
+ alignItems: 'center',
133
+ marginRight: 16,
134
+ },
135
+ textContainer: {
136
+ flex: 1,
137
+ marginRight: 8,
138
+ },
139
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Color Picker Component
3
+ *
4
+ * Simple color picker for theme customization
5
+ * Single Responsibility: Render color selection UI
6
+ */
7
+
8
+ import React, { useMemo, useCallback } from "react";
9
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
10
+ import {
11
+ AtomicIcon,
12
+ AtomicText,
13
+ useResponsiveDesignTokens,
14
+ } from "@umituz/react-native-design-system";
15
+
16
+ interface ColorPickerProps {
17
+ label: string;
18
+ value: string;
19
+ onValueChange: (color: string) => void;
20
+ colors: string[];
21
+ }
22
+
23
+ export const ColorPicker: React.FC<ColorPickerProps> = ({
24
+ label,
25
+ value,
26
+ onValueChange,
27
+ colors,
28
+ }) => {
29
+ const tokens = useResponsiveDesignTokens();
30
+
31
+ // Memoize styles to prevent unnecessary re-creation
32
+ const styles = useMemo(() => getStyles(tokens), [tokens]);
33
+
34
+ // Memoize colors array to prevent unnecessary re-renders
35
+ const colorsMemo = useMemo(() => colors, [colors]);
36
+
37
+ // Stable callback for color change to prevent infinite re-renders
38
+ const handleColorChange = useCallback((color: string) => {
39
+ try {
40
+ // Prevent unnecessary updates if color hasn't changed
41
+ if (value === color) return;
42
+
43
+ onValueChange(color);
44
+ } catch (error) {
45
+ if (__DEV__) {
46
+ console.error("[ColorPicker] Failed to change color:", error);
47
+ }
48
+ }
49
+ }, [value, onValueChange]);
50
+
51
+ // Memoize color options to prevent unnecessary re-renders
52
+ const colorOptions = useMemo(() => {
53
+ return colorsMemo.map((color) => {
54
+ const isSelected = value === color;
55
+
56
+ return (
57
+ <TouchableOpacity
58
+ key={color}
59
+ style={[
60
+ styles.colorOption,
61
+ { backgroundColor: color },
62
+ isSelected && styles.selectedColor,
63
+ ]}
64
+ onPress={() => handleColorChange(color)}
65
+ activeOpacity={0.8} // Performance optimization
66
+ >
67
+ {isSelected && (
68
+ <AtomicIcon name="checkmark" size="sm" customColor="#FFFFFF" />
69
+ )}
70
+ </TouchableOpacity>
71
+ );
72
+ });
73
+ }, [colorsMemo, value, handleColorChange, styles]);
74
+
75
+ return (
76
+ <View style={styles.container}>
77
+ <AtomicText type="bodyMedium" color="primary" style={styles.label}>
78
+ {label}
79
+ </AtomicText>
80
+ <View style={styles.colorRow}>
81
+ {colorOptions}
82
+ </View>
83
+ </View>
84
+ );
85
+ };
86
+
87
+ const getStyles = (tokens: ReturnType<typeof useResponsiveDesignTokens>) =>
88
+ StyleSheet.create({
89
+ container: {
90
+ marginBottom: 16,
91
+ },
92
+ label: {
93
+ marginBottom: 8,
94
+ },
95
+ colorRow: {
96
+ flexDirection: "row",
97
+ flexWrap: "wrap",
98
+ gap: 12,
99
+ },
100
+ colorOption: {
101
+ width: 40,
102
+ height: 40,
103
+ borderRadius: 20,
104
+ borderWidth: 2,
105
+ borderColor: tokens.colors.border,
106
+ justifyContent: "center",
107
+ alignItems: "center",
108
+ },
109
+ selectedColor: {
110
+ borderColor: tokens.colors.primary,
111
+ borderWidth: 3,
112
+ },
113
+ });