@umituz/react-native-settings 5.3.75 → 5.3.77

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.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Common Icon SVG Paths
3
+ *
4
+ * Centralized SVG paths for frequently used icons
5
+ * Works without external icon libraries
6
+ */
7
+ export declare const ICON_PATHS: {
8
+ readonly 'chevron-up': "M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z";
9
+ readonly 'chevron-down': "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z";
10
+ readonly 'chevron-forward': "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z";
11
+ readonly 'chevron-back': "M15.41 7.41L10.83 12l4.58 4.59L14 18l-6-6 6-6z";
12
+ readonly 'arrow-right': "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z";
13
+ readonly 'arrow-forward': "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z";
14
+ readonly 'arrow-left': "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z";
15
+ readonly close: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z";
16
+ readonly checkmark: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z";
17
+ readonly 'checkmark-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z";
18
+ readonly plus: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z";
19
+ readonly search: "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z";
20
+ readonly star: "M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z";
21
+ readonly 'alert-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z";
22
+ readonly 'information-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z";
23
+ readonly warning: "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z";
24
+ readonly 'chatbubble-outline': "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z";
25
+ readonly mail: "M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z";
26
+ readonly time: "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z";
27
+ readonly moon: "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z";
28
+ readonly users: "M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z";
29
+ readonly person: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z";
30
+ readonly create: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z";
31
+ readonly trash: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z";
32
+ readonly share: "M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z";
33
+ readonly globe: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z";
34
+ readonly settings: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z";
35
+ readonly heart: "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z";
36
+ readonly 'heart-outline': "M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z";
37
+ readonly notifications: "M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z";
38
+ readonly 'notifications-off': "M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm0-18c.83 0 1.5.67 1.5 1.5 0 .24-.05.46-.14.67l.94.94C14.74 6.57 15 5.82 15 5c0-1.66-1.34-3-3-3-.82 0-1.57.26-2.18.69l.95.95c.2-.09.43-.14.67-.14zM2.81 2.81L1.39 4.22l2.68 2.68C3.06 8.08 2.38 9.5 2.12 11H4.14c.24-1.12.76-2.13 1.48-2.96l1.94 1.94c-.06.34-.1.69-.1 1.02 0 2.48 1.51 4.5 4 4.5.33 0 .68-.04 1.02-.1l4.23 4.23c-.66.32-1.39.51-2.17.51-2.48 0-4.5-2.02-4.5-4.5h-2c0 2.93 1.81 5.45 4.38 6.51l2.5 2.5 1.41-1.41L2.81 2.81z";
39
+ };
40
+ export type IconPathName = keyof typeof ICON_PATHS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.3.75",
3
+ "version": "5.3.77",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification - expo-store-review and expo-device now lazy loaded",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./dist/index.d.ts",
@@ -10,6 +10,7 @@ import { View, TouchableOpacity, StyleSheet } from "react-native";
10
10
  import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system/atoms";
11
11
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
12
12
  import { isDev } from "../../../../utils/devUtils";
13
+ import { ICON_PATHS } from "../../../../utils/iconPaths";
13
14
 
14
15
  interface ColorPickerProps {
15
16
  label: string;
@@ -64,7 +65,11 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
64
65
  accessibilityHint={`Select ${color} color`}
65
66
  >
66
67
  {isSelected && (
67
- <AtomicIcon name="checkmark" size="sm" customColor={tokens.colors.textInverse} />
68
+ <AtomicIcon
69
+ svgPath={ICON_PATHS['checkmark']}
70
+ customSize={16}
71
+ customColor={tokens.colors.textInverse}
72
+ />
68
73
  )}
69
74
  </TouchableOpacity>
70
75
  );
@@ -27,6 +27,10 @@ export interface FAQItemProps {
27
27
  styles?: FAQItemStyles;
28
28
  }
29
29
 
30
+ // SVG paths for chevron icons
31
+ const CHEVRON_UP_PATH = "M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z";
32
+ const CHEVRON_DOWN_PATH = "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z";
33
+
30
34
  export const FAQItemComponent: React.FC<FAQItemProps> = React.memo(({
31
35
  item,
32
36
  isExpanded,
@@ -96,9 +100,9 @@ export const FAQItemComponent: React.FC<FAQItemProps> = React.memo(({
96
100
  </View>
97
101
  <View style={styles.iconContainer}>
98
102
  <AtomicIcon
99
- name={isExpanded ? 'chevron-up' : 'chevron-down'}
100
- size={20}
101
- color={isExpanded ? "onPrimary" : "secondary"}
103
+ svgPath={isExpanded ? CHEVRON_UP_PATH : CHEVRON_DOWN_PATH}
104
+ customSize={20}
105
+ customColor={isExpanded ? tokens.colors.onPrimary : tokens.colors.secondary}
102
106
  />
103
107
  </View>
104
108
  </TouchableOpacity>
@@ -8,6 +8,7 @@ import React, { useMemo } from 'react';
8
8
  import { View, TextInput, StyleSheet, ViewStyle, TextStyle } from 'react-native';
9
9
  import { AtomicIcon } from '@umituz/react-native-design-system/atoms';
10
10
  import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
11
+ import { ICON_PATHS } from '../../../../utils/iconPaths';
11
12
 
12
13
  export interface FAQSearchBarStyles {
13
14
  container?: ViewStyle;
@@ -58,7 +59,11 @@ export const FAQSearchBar: React.FC<FAQSearchBarProps> = ({
58
59
  return (
59
60
  <View style={[styles.container, customStyles?.container]}>
60
61
  <View style={styles.iconContainer}>
61
- <AtomicIcon name="search" size="md" color="secondary" />
62
+ <AtomicIcon
63
+ svgPath={ICON_PATHS['search']}
64
+ customSize={20}
65
+ customColor={tokens.colors.secondary}
66
+ />
62
67
  </View>
63
68
  <TextInput
64
69
  style={[styles.input, customStyles?.input]}
@@ -5,10 +5,10 @@
5
5
  */
6
6
 
7
7
  import React, { useMemo } from 'react';
8
- import { View, StyleSheet, ViewStyle, TextStyle, useWindowDimensions } from 'react-native';
8
+ import { View, StyleSheet, ViewStyle, TextStyle, useWindowDimensions, FlatList } from 'react-native';
9
9
  import { getContentMaxWidth } from '@umituz/react-native-design-system/device';
10
10
  import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
11
- import { useAppNavigation } from '@umituz/react-native-design-system/molecules';
11
+ import { NavigationHeader, useAppNavigation } from '@umituz/react-native-design-system/molecules';
12
12
  import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
13
13
  import { FAQCategory } from '../../domain/entities/FAQEntity';
14
14
  import { useFAQSearch } from '../hooks/useFAQSearch';
@@ -43,6 +43,7 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
43
43
  searchPlaceholder,
44
44
  emptySearchTitle,
45
45
  emptySearchMessage,
46
+ headerTitle,
46
47
  onBack,
47
48
  renderHeader,
48
49
  styles: customStyles,
@@ -75,45 +76,59 @@ export const FAQScreen: React.FC<FAQScreenProps> = ({
75
76
  }
76
77
  };
77
78
 
78
- const header = renderHeader ? renderHeader({ onBack: handleBack }) : null;
79
+ const header = renderHeader ? renderHeader({ onBack: handleBack }) : (
80
+ <NavigationHeader title={headerTitle} onBackPress={handleBack} />
81
+ );
82
+
83
+ // Render search bar as list header
84
+ const ListHeader = useMemo(() => (
85
+ <View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
86
+ <View style={[styles.searchBar, customStyles?.header]}>
87
+ <FAQSearchBar
88
+ value={searchQuery}
89
+ onChangeText={setSearchQuery}
90
+ placeholder={searchPlaceholder}
91
+ styles={customStyles?.searchBar}
92
+ />
93
+ </View>
94
+
95
+ {searchQuery && !hasResults && (
96
+ <FAQEmptyState
97
+ title={emptySearchTitle}
98
+ message={emptySearchMessage}
99
+ styles={customStyles?.emptyState}
100
+ />
101
+ )}
102
+ </View>
103
+ ), [searchQuery, hasResults, searchPlaceholder, emptySearchTitle, emptySearchMessage, customStyles, tokens, contentMaxWidth]);
104
+
105
+ const renderCategory = ({ item }: { item: FAQCategory }) => (
106
+ <View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
107
+ <FAQCategoryComponent
108
+ category={item}
109
+ isExpanded={isExpanded}
110
+ onToggleItem={toggleExpansion}
111
+ styles={customStyles?.category}
112
+ />
113
+ </View>
114
+ );
79
115
 
80
116
  return (
81
117
  <ScreenLayout
82
118
  edges={['top', 'bottom', 'left', 'right']}
83
- scrollable={true}
119
+ scrollable={false}
84
120
  header={header}
85
- contentContainerStyle={customStyles?.content}
86
121
  >
87
- <View style={{ alignSelf: 'center', width: '100%', maxWidth: contentMaxWidth }}>
88
- <View style={[styles.searchBar, customStyles?.header]}>
89
- <FAQSearchBar
90
- value={searchQuery}
91
- onChangeText={setSearchQuery}
92
- placeholder={searchPlaceholder}
93
- styles={customStyles?.searchBar}
94
- />
95
- </View>
96
-
97
- {searchQuery && !hasResults ? (
98
- <FAQEmptyState
99
- title={emptySearchTitle}
100
- message={emptySearchMessage}
101
- styles={customStyles?.emptyState}
102
- />
103
- ) : (
104
- filteredCategories.map((cat) => (
105
- <FAQCategoryComponent
106
- key={cat.id}
107
- category={cat}
108
- isExpanded={isExpanded}
109
- onToggleItem={toggleExpansion}
110
- styles={customStyles?.category}
111
- />
112
- ))
113
- )}
114
-
115
- <View style={styles.footer} />
116
- </View>
122
+ <FlatList
123
+ data={filteredCategories}
124
+ renderItem={renderCategory}
125
+ keyExtractor={(item) => item.id}
126
+ ListHeaderComponent={hasResults ? ListHeader : null}
127
+ ListEmptyComponent={!hasResults ? ListHeader : null}
128
+ ListFooterComponent={<View style={styles.footer} />}
129
+ showsVerticalScrollIndicator={false}
130
+ contentContainerStyle={{ paddingBottom: tokens.spacing.xl }}
131
+ />
117
132
  </ScreenLayout>
118
133
  );
119
134
  };
@@ -1,9 +1,9 @@
1
- import React, { useState, useCallback } from "react";
1
+ import React, { useState, useCallback, useMemo } from "react";
2
2
  import {
3
3
  View,
4
4
  StyleSheet,
5
5
  TouchableOpacity,
6
- ScrollView,
6
+ FlatList,
7
7
  ActivityIndicator,
8
8
  } from "react-native";
9
9
  import {
@@ -13,6 +13,7 @@ import {
13
13
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
14
14
  import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
15
15
  import { FeedbackModal } from "../components/FeedbackModal";
16
+ import { ICON_PATHS } from "../../../../utils/iconPaths";
16
17
  import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
17
18
  import type { FeedbackRating } from "../../domain/entities/FeedbackEntity";
18
19
  import type { FeatureRequestItem } from "../../domain/entities/FeatureRequestEntity";
@@ -82,7 +83,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
82
83
  }
83
84
  };
84
85
 
85
- const filteredRequests = (() => {
86
+ const filteredRequests = useMemo(() => {
86
87
  switch (activeTab) {
87
88
  case 'my':
88
89
  return requests.filter(r => r.createdBy === userId);
@@ -91,22 +92,30 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
91
92
  default:
92
93
  return requests;
93
94
  }
94
- })();
95
+ }, [requests, activeTab, userId]);
95
96
 
96
- const renderRequestCard = (item: FeatureRequestItem) => {
97
+ const renderRequestCard = useCallback(({ item }: { item: FeatureRequestItem }) => {
97
98
  const voted = userVotes[item.id] || null;
98
99
 
99
100
  return (
100
101
  <View key={item.id} style={[styles.card, { backgroundColor: tokens.colors.surfaceSecondary, borderColor: tokens.colors.borderLight }]}>
101
102
  <View style={styles.voteColumn}>
102
103
  <TouchableOpacity onPress={() => vote(item.id, 'up')}>
103
- <AtomicIcon name="chevron-up" size="md" color={voted === 'up' ? "primary" : "textSecondary"} />
104
+ <AtomicIcon
105
+ svgPath={ICON_PATHS['chevron-up']}
106
+ customSize={20}
107
+ customColor={voted === 'up' ? tokens.colors.primary : tokens.colors.textSecondary}
108
+ />
104
109
  </TouchableOpacity>
105
110
  <AtomicText style={[styles.voteCount, { color: voted === 'up' ? tokens.colors.primary : tokens.colors.textPrimary }]}>
106
111
  {item.votes}
107
112
  </AtomicText>
108
113
  <TouchableOpacity onPress={() => vote(item.id, 'down')}>
109
- <AtomicIcon name="chevron-down" size="md" color={voted === 'down' ? "primary" : "textSecondary"} />
114
+ <AtomicIcon
115
+ svgPath={ICON_PATHS['chevron-down']}
116
+ customSize={20}
117
+ customColor={voted === 'down' ? tokens.colors.primary : tokens.colors.textSecondary}
118
+ />
110
119
  </TouchableOpacity>
111
120
  </View>
112
121
 
@@ -135,19 +144,27 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
135
144
  </View>
136
145
  </View>
137
146
  );
138
- };
147
+ }, [userVotes, vote, tokens.colors, getStatusColor, statusLabels, t]);
148
+
149
+ const keyExtractor = useCallback((item: FeatureRequestItem) => item.id, []);
139
150
 
140
- const header = (
151
+ const tabs = useMemo(() => (['all', 'my', 'roadmap'] as const), []);
152
+
153
+ const header = useMemo(() => (
141
154
  <View style={styles.header}>
142
155
  <AtomicText style={styles.headerTitle}>{screenTitle}</AtomicText>
143
156
  <TouchableOpacity
144
157
  style={[styles.addButton, { backgroundColor: tokens.colors.primary }]}
145
158
  onPress={() => setIsModalVisible(true)}
146
159
  >
147
- <AtomicIcon name="plus" size="sm" color="onPrimary" />
160
+ <AtomicIcon
161
+ svgPath={ICON_PATHS['plus']}
162
+ customSize={16}
163
+ customColor={tokens.colors.onPrimary}
164
+ />
148
165
  </TouchableOpacity>
149
166
  </View>
150
- );
167
+ ), [screenTitle, tokens.colors.primary, tokens.colors.onPrimary]);
151
168
 
152
169
  if (isLoading) {
153
170
  return (
@@ -162,7 +179,7 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
162
179
  return (
163
180
  <ScreenLayout header={header} edges={['top', 'bottom']}>
164
181
  <View style={styles.tabsContainer}>
165
- {(['all', 'my', 'roadmap'] as const).map((tab) => (
182
+ {tabs.map((tab) => (
166
183
  <TouchableOpacity
167
184
  key={tab}
168
185
  onPress={() => setActiveTab(tab)}
@@ -175,35 +192,50 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
175
192
  ))}
176
193
  </View>
177
194
 
178
- <ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
179
- <View style={[styles.banner, { backgroundColor: tokens.colors.primary + '10', borderColor: tokens.colors.primary + '20' }]}>
180
- <View style={styles.bannerIconContainer}>
181
- <AtomicIcon name="users" size="lg" color="primary" />
182
- <View style={styles.pulseDot} />
183
- </View>
184
- <View>
185
- <AtomicText style={styles.bannerTitle}>{bannerTitle}</AtomicText>
186
- <AtomicText style={[styles.bannerSub, { color: tokens.colors.textSecondary }]}>
187
- {bannerSub}
188
- </AtomicText>
189
- </View>
190
- </View>
191
-
192
- <AtomicText style={styles.sectionTitle}>{trendingTitle}</AtomicText>
195
+ <FlatList
196
+ data={filteredRequests}
197
+ renderItem={renderRequestCard}
198
+ keyExtractor={(item) => item.id}
199
+ ListHeaderComponent={
200
+ <View style={styles.container}>
201
+ <View style={[styles.banner, { backgroundColor: tokens.colors.primary + '10', borderColor: tokens.colors.primary + '20' }]}>
202
+ <View style={styles.bannerIconContainer}>
203
+ <AtomicIcon
204
+ svgPath={ICON_PATHS['users']}
205
+ customSize={24}
206
+ customColor={tokens.colors.primary}
207
+ />
208
+ <View style={styles.pulseDot} />
209
+ </View>
210
+ <View>
211
+ <AtomicText style={styles.bannerTitle}>{bannerTitle}</AtomicText>
212
+ <AtomicText style={[styles.bannerSub, { color: tokens.colors.textSecondary }]}>
213
+ {bannerSub}
214
+ </AtomicText>
215
+ </View>
216
+ </View>
193
217
 
194
- {filteredRequests.length === 0 ? (
195
- <View style={styles.emptyState}>
196
- <AtomicIcon name="chatbubble-outline" size="xl" color="textTertiary" />
218
+ <AtomicText style={styles.sectionTitle}>{trendingTitle}</AtomicText>
219
+ </View>
220
+ }
221
+ ListEmptyComponent={
222
+ <View style={[styles.emptyState, { paddingHorizontal: tokens.spacing.md }]}>
223
+ <AtomicIcon
224
+ svgPath={ICON_PATHS['chatbubble-outline']}
225
+ customSize={32}
226
+ customColor={tokens.colors.textTertiary}
227
+ />
197
228
  <AtomicText style={[styles.emptyText, { color: tokens.colors.textTertiary }]}>
198
229
  {t.empty || "No requests yet. Be the first!"}
199
230
  </AtomicText>
200
231
  </View>
201
- ) : (
202
- <View style={styles.listContent}>
203
- {filteredRequests.map(renderRequestCard)}
204
- </View>
205
- )}
206
- </ScrollView>
232
+ }
233
+ contentContainerStyle={styles.listContent}
234
+ showsVerticalScrollIndicator={false}
235
+ removeClippedSubviews={true}
236
+ maxToRenderPerBatch={10}
237
+ windowSize={5}
238
+ />
207
239
 
208
240
  {isModalVisible && (
209
241
  <FeedbackModal
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React, { useMemo, useCallback } from 'react';
7
- import { View, StyleSheet, TouchableOpacity } from 'react-native';
7
+ import { View, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
8
8
  import { AtomicText, AtomicIcon, AtomicSpinner } from '@umituz/react-native-design-system/atoms';
9
9
  import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
10
10
  import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
@@ -54,10 +54,45 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
54
54
 
55
55
  const fab = canAddMore ? (
56
56
  <TouchableOpacity style={styles.fab} onPress={onAddPress} activeOpacity={0.8}>
57
- <AtomicIcon name="add" size="md" color="onPrimary" />
57
+ <AtomicIcon
58
+ svgPath="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
59
+ customSize={20}
60
+ customColor={tokens.colors.onPrimary}
61
+ />
58
62
  <AtomicText type="bodyMedium" style={styles.fabText}>{translations.addButtonLabel}</AtomicText>
59
63
  </TouchableOpacity>
60
- ) : undefined;
64
+ ) : null;
65
+
66
+ const renderEmptyState = useCallback(() => (
67
+ <View style={styles.emptyContainer}>
68
+ <View style={styles.emptyIconContainer}>
69
+ <AtomicIcon
70
+ svgPath="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-14c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 18c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
71
+ customSize={48}
72
+ customColor={tokens.colors.secondary}
73
+ />
74
+ </View>
75
+ <AtomicText type="bodyLarge" style={styles.emptyTitle}>{translations.emptyTitle}</AtomicText>
76
+ <AtomicText type="bodySmall" style={styles.emptyDescription}>{translations.emptyDescription}</AtomicText>
77
+ </View>
78
+ ), [translations.emptyTitle, translations.emptyDescription, tokens.colors]);
79
+
80
+ const renderItem = useCallback(({ item }: { item: Reminder }) => (
81
+ <ReminderItem
82
+ reminder={item}
83
+ translations={{
84
+ frequencyOnce: translations.frequencyOnce,
85
+ frequencyDaily: translations.frequencyDaily,
86
+ frequencyWeekly: translations.frequencyWeekly,
87
+ frequencyMonthly: translations.frequencyMonthly,
88
+ }}
89
+ onToggle={handleToggle}
90
+ onEdit={onEditPress}
91
+ onDelete={handleDelete}
92
+ />
93
+ ), [translations, handleToggle, onEditPress, handleDelete]);
94
+
95
+ const keyExtractor = useCallback((item: Reminder) => item.id, []);
61
96
 
62
97
  if (isLoading) {
63
98
  return (
@@ -68,32 +103,20 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
68
103
  }
69
104
 
70
105
  return (
71
- <ScreenLayout footer={fab} contentContainerStyle={styles.listContent}>
72
- {reminders.length === 0 ? (
73
- <View style={styles.emptyContainer}>
74
- <View style={styles.emptyIconContainer}>
75
- <AtomicIcon name="notifications-off" size="xl" color="secondary" />
76
- </View>
77
- <AtomicText type="bodyLarge" style={styles.emptyTitle}>{translations.emptyTitle}</AtomicText>
78
- <AtomicText type="bodySmall" style={styles.emptyDescription}>{translations.emptyDescription}</AtomicText>
79
- </View>
80
- ) : (
81
- reminders.map((reminder) => (
82
- <ReminderItem
83
- key={reminder.id}
84
- reminder={reminder}
85
- translations={{
86
- frequencyOnce: translations.frequencyOnce,
87
- frequencyDaily: translations.frequencyDaily,
88
- frequencyWeekly: translations.frequencyWeekly,
89
- frequencyMonthly: translations.frequencyMonthly,
90
- }}
91
- onToggle={handleToggle}
92
- onEdit={onEditPress}
93
- onDelete={handleDelete}
94
- />
95
- ))
96
- )}
106
+ <ScreenLayout footer={fab} scrollable={false}>
107
+ <FlatList
108
+ data={reminders}
109
+ renderItem={renderItem}
110
+ keyExtractor={keyExtractor}
111
+ ListEmptyComponent={renderEmptyState}
112
+ contentContainerStyle={reminders.length === 0 ? styles.listEmptyContent : styles.listContent}
113
+ showsVerticalScrollIndicator={false}
114
+ removeClippedSubviews={true}
115
+ maxToRenderPerBatch={10}
116
+ updateCellsBatchingPeriod={50}
117
+ initialNumToRender={8}
118
+ windowSize={5}
119
+ />
97
120
  </ScreenLayout>
98
121
  );
99
122
  };
@@ -101,6 +124,7 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
101
124
  const createStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
102
125
  StyleSheet.create({
103
126
  listContent: { padding: 16, flexGrow: 1 },
127
+ listEmptyContent: { flexGrow: 1 },
104
128
  emptyContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 32, paddingTop: 100 },
105
129
  emptyIconContainer: {
106
130
  width: 80,
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Common Icon SVG Paths
3
+ *
4
+ * Centralized SVG paths for frequently used icons
5
+ * Works without external icon libraries
6
+ */
7
+
8
+ export const ICON_PATHS = {
9
+ // Navigation
10
+ 'chevron-up': "M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z",
11
+ 'chevron-down': "M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z",
12
+ 'chevron-forward': "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z",
13
+ 'chevron-back': "M15.41 7.41L10.83 12l4.58 4.59L14 18l-6-6 6-6z",
14
+ 'arrow-right': "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z",
15
+ 'arrow-forward': "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z",
16
+ 'arrow-left': "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z",
17
+
18
+ // UI Elements
19
+ 'close': "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
20
+ 'checkmark': "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z",
21
+ 'checkmark-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z",
22
+ 'plus': "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",
23
+ 'search': "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",
24
+
25
+ // Status & Feedback
26
+ 'star': "M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z",
27
+ 'alert-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z",
28
+ 'information-circle': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z",
29
+ 'warning': "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z",
30
+
31
+ // Communication
32
+ 'chatbubble-outline': "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z",
33
+ 'mail': "M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z",
34
+
35
+ // Time & Notifications
36
+ 'time': "M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z",
37
+ 'moon': "M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z",
38
+
39
+ // Users & Social
40
+ 'users': "M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z",
41
+ 'person': "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z",
42
+
43
+ // Actions
44
+ 'create': "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z",
45
+ 'trash': "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
46
+ 'share': "M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z",
47
+
48
+ // Misc
49
+ 'globe': "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z",
50
+ 'settings': "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z",
51
+ 'heart': "M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z",
52
+ 'heart-outline': "M16.5 3c-1.74 0-3.41.81-4.5 2.09C10.91 3.81 9.24 3 7.5 3 4.42 3 2 5.42 2 8.5c0 3.78 3.4 6.86 8.55 11.54L12 21.35l1.45-1.32C18.6 15.36 22 12.28 22 8.5 22 5.42 19.58 3 16.5 3zm-4.4 15.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z",
53
+
54
+ // Notifications
55
+ 'notifications': "M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm6-6v-5c0-3.07-1.63-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.64 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2zm-2 1H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6z",
56
+ 'notifications-off': "M12 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zm0-18c.83 0 1.5.67 1.5 1.5 0 .24-.05.46-.14.67l.94.94C14.74 6.57 15 5.82 15 5c0-1.66-1.34-3-3-3-.82 0-1.57.26-2.18.69l.95.95c.2-.09.43-.14.67-.14zM2.81 2.81L1.39 4.22l2.68 2.68C3.06 8.08 2.38 9.5 2.12 11H4.14c.24-1.12.76-2.13 1.48-2.96l1.94 1.94c-.06.34-.1.69-.1 1.02 0 2.48 1.51 4.5 4 4.5.33 0 .68-.04 1.02-.1l4.23 4.23c-.66.32-1.39.51-2.17.51-2.48 0-4.5-2.02-4.5-4.5h-2c0 2.93 1.81 5.45 4.38 6.51l2.5 2.5 1.41-1.41L2.81 2.81z",
57
+ } as const;
58
+
59
+ export type IconPathName = keyof typeof ICON_PATHS;