@umituz/react-native-design-system 2.9.70 → 2.10.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-design-system",
3
- "version": "2.9.70",
3
+ "version": "2.10.0",
4
4
  "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, onboarding, and loading utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -63,7 +63,6 @@
63
63
  "url": "https://github.com/umituz/react-native-design-system"
64
64
  },
65
65
  "peerDependencies": {
66
- "@expo/vector-icons": ">=15.0.0",
67
66
  "@react-native-async-storage/async-storage": ">=1.18.0",
68
67
  "@react-native-community/datetimepicker": ">=8.0.0",
69
68
  "@react-navigation/bottom-tabs": ">=7.0.0",
@@ -103,7 +102,6 @@
103
102
  },
104
103
  "devDependencies": {
105
104
  "@eslint/js": "^9.39.2",
106
- "@expo/vector-icons": "^15.0.0",
107
105
  "@react-native-async-storage/async-storage": "^2.2.0",
108
106
  "@react-native-community/datetimepicker": "^8.5.1",
109
107
  "@react-navigation/bottom-tabs": "^7.9.0",
@@ -19,7 +19,7 @@ export interface AtomicBadgeProps {
19
19
  variant?: BadgeVariant;
20
20
  /** Size preset */
21
21
  size?: BadgeSize;
22
- /** Optional icon name (Ionicons) */
22
+ /** Optional icon name (interpreted by app's icon renderer) */
23
23
  icon?: IconName;
24
24
  /** Icon position */
25
25
  iconPosition?: "left" | "right";
@@ -1,316 +1,185 @@
1
1
  # AtomicIcon
2
2
 
3
- A theme-aware icon component using Ionicons with semantic sizing and colors.
3
+ A theme-aware, **icon-library agnostic** icon component with semantic sizing and colors.
4
+
5
+ ## Setup - Icon Renderer (Required)
6
+
7
+ AtomicIcon requires an icon renderer to be provided via `DesignSystemProvider`. This allows your app to use ANY icon library.
8
+
9
+ ```tsx
10
+ // App.tsx or your root component
11
+ import { Ionicons } from '@expo/vector-icons';
12
+ // OR MaterialIcons, Feather, FontAwesome, etc.
13
+
14
+ <DesignSystemProvider
15
+ iconRenderer={({ name, size, color }) => (
16
+ <Ionicons name={name} size={size} color={color} />
17
+ )}
18
+ >
19
+ <App />
20
+ </DesignSystemProvider>
21
+ ```
4
22
 
5
23
  ## Import & Usage
6
24
 
7
25
  ```typescript
8
- import { AtomicIcon } from 'react-native-design-system/src/atoms/AtomicIcon';
26
+ import { AtomicIcon } from '@umituz/react-native-design-system';
9
27
  ```
10
28
 
11
- **Location:** `src/atoms/AtomicIcon.tsx`
12
-
13
29
  ## Basic Usage
14
30
 
15
31
  ```tsx
16
32
  <AtomicIcon name="heart" />
33
+ <AtomicIcon name="heart" size="lg" color="primary" />
34
+ <AtomicIcon name="settings" customSize={32} customColor="#FF0000" />
17
35
  ```
18
36
 
19
37
  ## Strategy
20
38
 
21
- **Purpose**: Provide consistent, accessible icons with theme integration.
22
-
23
- **When to Use**:
24
- - Navigation icons (tabs, headers, buttons)
25
- - Action indicators (favorites, settings, search)
26
- - Status indicators (success, error, warning)
27
- - Decorative icons with semantic meaning
28
-
29
- **When NOT to Use**:
30
- - For images or photos (use Image component)
31
- - When custom icon graphics are needed (use SVG)
32
- - For non-icon graphics or illustrations
33
-
34
- ## Rules
39
+ **Purpose**: Provide consistent, accessible icons with theme integration while allowing apps to choose their icon library.
35
40
 
36
- ### Required
41
+ **Key Benefits**:
42
+ - ✅ Use ANY icon library (Ionicons, MaterialIcons, Feather, FontAwesome, etc.)
43
+ - ✅ Theme-aware semantic colors
44
+ - ✅ Consistent sizing across the app
45
+ - ✅ No forced dependencies
37
46
 
38
- 1. **MUST** provide `name` prop (valid Ionicons name)
39
- 2. **ALWAYS** use appropriate size for context
40
- 3. **SHOULD** use semantic colors when meaningful
41
- 4. **MUST** provide accessibility label if not decorative
42
- 5. **ALWAYS** validate icon name exists
43
- 6. **SHOULD** use consistent sizing within context
44
- 7. **MUST** handle invalid icon names gracefully
45
-
46
- ### Size Guidelines
47
+ ## Props Reference
47
48
 
48
- 1. **xs (16px)**: Inline text, tiny badges
49
- 2. **sm (20px)**: List items, compact buttons
50
- 3. **md (24px)**: Default, most use cases
51
- 4. **lg (28px)**: Emphasis, large buttons
52
- 5. **xl (32px)**: Headers, featured icons
49
+ | Prop | Type | Required | Default | Description |
50
+ |------|------|----------|---------|-------------|
51
+ | `name` | `string` | Yes | - | Icon name (interpreted by your renderer) |
52
+ | `size` | `IconSize` | No | `'md'` | Semantic size preset |
53
+ | `customSize` | `number` | No | - | Custom size in pixels |
54
+ | `color` | `IconColor` | No | - | Semantic color from theme |
55
+ | `customColor` | `string` | No | - | Custom color (hex, rgba, etc.) |
56
+ | `svgPath` | `string` | No | - | Custom SVG path (built-in rendering) |
57
+ | `svgViewBox` | `string` | No | `'0 0 24 24'` | SVG viewBox |
58
+ | `withBackground` | `boolean` | No | `false` | Add circular background |
59
+ | `backgroundColor` | `string` | No | - | Background color |
60
+ | `accessibilityLabel` | `string` | No | - | Accessibility label |
61
+ | `testID` | `string` | No | - | Test ID |
62
+ | `style` | `StyleProp<ViewStyle>` | No | - | Additional styles |
53
63
 
54
- ### Color Semantics
64
+ ## Size Presets
55
65
 
56
- 1. **primary**: Primary actions, active states
57
- 2. **success**: Success states, confirmations
58
- 3. **warning**: Warning states, cautions
59
- 4. **error**: Error states, destructive actions
60
- 5. **secondary**: Secondary actions, inactive states
66
+ | Size | Pixels | Use Case |
67
+ |------|--------|----------|
68
+ | `xs` | 12px | Inline text, tiny badges |
69
+ | `sm` | 16px | List items, compact buttons |
70
+ | `md` | 20px | Default, most use cases |
71
+ | `lg` | 24px | Emphasis, large buttons |
72
+ | `xl` | 32px | Headers, featured icons |
73
+ | `xxl` | 48px | Hero icons, large displays |
61
74
 
62
- ### Background Usage
75
+ ## Semantic Colors
63
76
 
64
- 1. **Use for**: Floating action buttons, avatar icons
65
- 2. **Don't overuse**: Not every icon needs background
66
- 3. **Match colors**: Background should complement icon
77
+ | Color | Use Case |
78
+ |-------|----------|
79
+ | `primary` | Primary actions, active states |
80
+ | `secondary` | Secondary actions, inactive states |
81
+ | `success` | Success states, confirmations |
82
+ | `warning` | Warning states, cautions |
83
+ | `error` | Error states, destructive actions |
84
+ | `info` | Information states |
85
+ | `textPrimary` | Default text color |
86
+ | `textSecondary` | Subdued text |
67
87
 
68
- ## Forbidden
88
+ ## Examples
69
89
 
70
- **NEVER** do these:
90
+ ### Different Icon Libraries
71
91
 
72
92
  ```tsx
73
- // ❌ No icon name
74
- <AtomicIcon /> {/* ❌ Missing required prop */}
75
-
76
- // Invalid icon name
77
- <AtomicIcon name="invalid-icon-name" /> {/* ❌ Shows fallback */}
78
-
79
- // ❌ Wrong size for context
80
- <Button>
81
- <AtomicIcon name="add" size="xxl" /> {/* ❌ Too large */}
82
- </Button>
83
-
84
- // ❌ Inconsistent sizes
85
- <View style={{ flexDirection: 'row' }}>
86
- <AtomicIcon name="home" size="xs" />
87
- <AtomicIcon name="settings" size="xl" /> {/* ❌ Inconsistent */}
88
- </View>
89
-
90
- // ❌ Decorative icon not hidden
91
- <AtomicIcon
92
- name="sparkles"
93
- // ❌ Should have accessibilityElementsHidden
93
+ // Ionicons
94
+ <DesignSystemProvider
95
+ iconRenderer={({ name, size, color }) => (
96
+ <Ionicons name={name} size={size} color={color} />
97
+ )}
94
98
  />
95
99
 
96
- // Confusing color semantics
97
- <AtomicIcon
98
- name="trash"
99
- color="success" {/* Trash should be error/danger */}
100
+ // Material Icons
101
+ <DesignSystemProvider
102
+ iconRenderer={({ name, size, color }) => (
103
+ <MaterialIcons name={name} size={size} color={color} />
104
+ )}
100
105
  />
101
106
 
102
- // Background for every icon
103
- <AtomicIcon
104
- name="home"
105
- withBackground {/* Unnecessary */}
107
+ // Feather Icons
108
+ <DesignSystemProvider
109
+ iconRenderer={({ name, size, color }) => (
110
+ <Feather name={name} size={size} color={color} />
111
+ )}
106
112
  />
107
- ```
108
-
109
- ## Best Practices
110
-
111
- ### Size Selection
112
-
113
- ✅ **DO**:
114
- ```tsx
115
- // ✅ Inline with text
116
- <AtomicText>
117
- <AtomicIcon name="star" size="xs" /> Featured
118
- </AtomicText>
119
-
120
- // ✅ Button icons
121
- <Button>
122
- <AtomicIcon name="add" size="sm" />
123
- </Button>
124
-
125
- // ✅ Tab icons
126
- <TabBar>
127
- <TabIcon icon="home" size="md" />
128
- </TabBar>
129
- ```
130
-
131
- ❌ **DON'T**:
132
- ```tsx
133
- // ❌ Wrong sizes
134
- <Button>
135
- <AtomicIcon name="add" size="xl" /> {/* Too large */}
136
- </Button>
137
-
138
- <AtomicText>
139
- <AtomicIcon name="star" size="xl" /> Featured {/* Too large */}
140
- </AtomicText>
141
- ```
142
-
143
- ### Semantic Colors
144
113
 
145
- **DO**:
146
- ```tsx
147
- // Meaningful colors
148
- <AtomicIcon name="checkmark-circle" color="success" />
149
- <AtomicIcon name="warning" color="warning" />
150
- <AtomicIcon name="close-circle" color="error" />
151
- <AtomicIcon name="heart" color="primary" />
152
- ```
153
-
154
- ❌ **DON'T**:
155
- ```tsx
156
- // ❌ Confusing colors
157
- <AtomicIcon name="trash" color="success" />
158
- <AtomicIcon name="checkmark" color="error" />
114
+ // Custom SVG Icons
115
+ <DesignSystemProvider
116
+ iconRenderer={({ name, size, color }) => (
117
+ <MySvgIcon name={name} width={size} height={size} fill={color} />
118
+ )}
119
+ />
159
120
  ```
160
121
 
161
- ### Background Usage
122
+ ### With Background
162
123
 
163
- ✅ **DO**:
164
124
  ```tsx
165
- // ✅ FAB icons
166
125
  <AtomicIcon
167
126
  name="add"
168
127
  size="md"
169
- withBackground
170
128
  color="primary"
171
- />
172
-
173
- // ✅ Status icons
174
- <AtomicIcon
175
- name="checkmark"
176
- size="sm"
177
129
  withBackground
178
- color="success"
179
- backgroundColor="#d4edda"
130
+ backgroundColor="#E3F2FD"
180
131
  />
181
132
  ```
182
133
 
183
- **DON'T**:
134
+ ### Custom SVG Path (No Renderer Needed)
135
+
184
136
  ```tsx
185
- // ❌ Unnecessary background
186
137
  <AtomicIcon
187
- name="home"
188
- withBackground {/* Not needed */}
138
+ svgPath="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"
139
+ svgViewBox="0 0 24 24"
140
+ color="primary"
189
141
  />
190
142
  ```
191
143
 
192
- ## AI Coding Guidelines
193
-
194
- ### For AI Agents
195
-
196
- When generating AtomicIcon components, follow these rules:
197
-
198
- 1. **Always provide valid icon name**:
199
- ```tsx
200
- // ✅ Good - valid Ionicons
201
- <AtomicIcon name="home" />
202
- <AtomicIcon name="settings-outline" />
203
- <AtomicIcon name="chevron-forward" />
204
-
205
- // ❌ Bad - invalid names
206
- <AtomicIcon name="invalid-icon" />
207
- <AtomicIcon name="home_icon" />
208
- ```
209
-
210
- 2. **Always use appropriate size**:
211
- ```tsx
212
- // ✅ Good - size matches context
213
- <Button>
214
- <AtomicIcon name="add" size="sm" />
215
- </Button>
216
- <TabIcon icon="home" size="md" />
217
-
218
- // ❌ Bad - wrong size
219
- <Button>
220
- <AtomicIcon name="add" size="xl" />
221
- </Button>
222
- ```
223
-
224
- 3. **Always use semantic colors meaningfully**:
225
- ```tsx
226
- // ✅ Good - meaningful colors
227
- <AtomicIcon name="checkmark" color="success" />
228
- <AtomicIcon name="warning" color="warning" />
229
- <AtomicIcon name="trash" color="error" />
230
-
231
- // ❌ Bad - confusing colors
232
- <AtomicIcon name="trash" color="success" />
233
- ```
234
-
235
- 4. **Always provide accessibility context**:
236
- ```tsx
237
- // ✅ Good - accessible
238
- <AtomicIcon
239
- name="menu"
240
- accessibilityLabel="Open menu"
241
- accessibilityRole="button"
242
- />
243
-
244
- // ❌ Bad - not accessible
245
- <AtomicIcon name="menu" />
246
- ```
247
-
248
- ### Common Patterns
249
-
250
- #### Basic Icon
251
- ```tsx
252
- <AtomicIcon name="heart" />
253
- ```
254
-
255
- #### With Text
256
- ```tsx
257
- <View style={{ flexDirection: 'row', alignItems: 'center' }}>
258
- <AtomicIcon name="star" size="sm" color="warning" />
259
- <AtomicText>Featured</AtomicText>
260
- </View>
261
- ```
262
-
263
- #### Button Icon
264
- ```tsx
265
- <Button onPress={handleAction}>
266
- <AtomicIcon name="add" size="sm" color="white" />
267
- </Button>
268
- ```
144
+ ## Accessibility
269
145
 
270
- #### Status Icon
271
146
  ```tsx
272
147
  <AtomicIcon
273
- name="checkmark-circle"
274
- size="lg"
275
- color="success"
276
- accessibilityLabel="Completed"
148
+ name="menu"
149
+ accessibilityLabel="Open navigation menu"
277
150
  />
278
151
  ```
279
152
 
280
- ## Props Reference
281
-
282
- | Prop | Type | Required | Default | Description |
283
- |------|------|----------|---------|-------------|
284
- | `name` | `IconName` | Yes | - | Ionicons icon name |
285
- | `size` | `IconSize` | No | `'md'` | Icon size |
286
- | `customSize` | `number` | No | - | Custom size (px) |
287
- | `color` | `IconColor` | No | - | Semantic color |
288
- | `customColor` | `string` | No | - | Custom color |
289
- | `svgPath` | `string` | No | - | Custom SVG path |
290
- | `svgViewBox` | `string` | No | `'0 0 24 24'` | SVG viewBox |
291
- | `withBackground` | `boolean` | No | `false` | Circular background |
292
- | `backgroundColor` | `string` | No | - | Background color |
293
- | `accessibilityLabel` | `string` | No | - | Accessibility label |
153
+ ## Migration from Ionicons-specific
294
154
 
295
- ## Accessibility
155
+ If you were using the old Ionicons-specific AtomicIcon:
296
156
 
297
- - Screen reader support
298
- - Accessibility label
299
- - Semantic role
300
- - ✅ Test ID support
157
+ 1. Add `iconRenderer` to your `DesignSystemProvider`
158
+ 2. Install your preferred icon library
159
+ 3. Icon names remain the same if using Ionicons
301
160
 
302
- ## Performance Tips
161
+ ```tsx
162
+ // Before (implicit Ionicons)
163
+ <AtomicIcon name="heart" />
303
164
 
304
- 1. **React.memo**: Component is already memoized
305
- 2. **Static names**: Use constant icon names
306
- 3. **Avoid re-renders**: Stabilize icon props
165
+ // After (explicit Ionicons via renderer)
166
+ <DesignSystemProvider
167
+ iconRenderer={({ name, size, color }) => (
168
+ <Ionicons name={name} size={size} color={color} />
169
+ )}
170
+ >
171
+ <AtomicIcon name="heart" /> {/* Same usage */}
172
+ </DesignSystemProvider>
173
+ ```
307
174
 
308
- ## Related Components
175
+ ## Performance
309
176
 
310
- - [`AtomicButton`](./AtomicButton.README.md) - Button component
311
- - [`AtomicChip`](./AtomicChip.README.md) - Chip component
312
- - [`AtomicText`](./AtomicText.README.md) - Text component
177
+ - Component is memoized with `React.memo`
178
+ - Use stable icon names (avoid dynamic strings when possible)
179
+ - Icon renderer is cached via Provider
313
180
 
314
- ## License
181
+ ## Related
315
182
 
316
- MIT
183
+ - [`IconProvider`](./IconRegistry.tsx) - Icon registry for custom renderers
184
+ - [`AtomicButton`](./button/README.md) - Button with icon support
185
+ - [`AtomicBadge`](./AtomicBadge.tsx) - Badge with icon support
@@ -1,30 +1,42 @@
1
1
  /**
2
- * AtomicIcon - Theme-aware Icon Component
2
+ * AtomicIcon - Agnostic Icon Component
3
3
  *
4
- * Uses @expo/vector-icons/Ionicons internally
5
- * Adds theme-aware semantic colors and background support
4
+ * This component is completely icon-library agnostic.
5
+ * Apps MUST provide their own icon renderer via DesignSystemProvider.
6
+ *
7
+ * @example
8
+ * // In your app, provide an icon renderer:
9
+ * import { MaterialIcons } from '@expo/vector-icons';
10
+ *
11
+ * <DesignSystemProvider
12
+ * iconRenderer={({ name, size, color }) => (
13
+ * <MaterialIcons name={name} size={size} color={color} />
14
+ * )}
15
+ * >
16
+ * <App />
17
+ * </DesignSystemProvider>
18
+ *
19
+ * // Then use AtomicIcon anywhere:
20
+ * <AtomicIcon name="favorite" size="md" color="primary" />
6
21
  */
7
22
 
8
23
  import React from "react";
9
24
  import { View, StyleSheet, StyleProp, ViewStyle } from "react-native";
10
- import { Ionicons } from "@expo/vector-icons";
11
25
  import Svg, { Path } from "react-native-svg";
12
- import { useAppDesignTokens } from '../theme';
26
+ import { useAppDesignTokens } from "../theme";
27
+ import { useIconRenderer, type IconRenderProps } from "./IconRegistry";
13
28
  import {
14
29
  type IconSize as BaseIconSize,
15
- type IconName,
16
30
  type IconColor,
17
31
  } from "./AtomicIcon.types";
18
32
 
19
- // Re-export IconSize for convenience
20
33
  export type IconSize = BaseIconSize;
21
- export type { IconName, IconColor };
22
-
23
- const FALLBACK_ICON = "help-circle-outline";
34
+ export type IconName = string;
35
+ export type { IconColor, IconRenderProps };
24
36
 
25
37
  export interface AtomicIconProps {
26
- /** Icon name (Ionicons) */
27
- name?: IconName;
38
+ /** Icon name - interpreted by the app's icon renderer */
39
+ name?: string;
28
40
  /** Semantic size preset */
29
41
  size?: IconSize;
30
42
  /** Custom size in pixels (overrides size) */
@@ -33,7 +45,7 @@ export interface AtomicIconProps {
33
45
  color?: IconColor;
34
46
  /** Custom color (overrides color) */
35
47
  customColor?: string;
36
- /** Custom SVG path for generic icons */
48
+ /** Custom SVG path for inline SVG icons */
37
49
  svgPath?: string;
38
50
  /** ViewBox for custom SVG (default: 0 0 24 24) */
39
51
  svgViewBox?: string;
@@ -74,123 +86,151 @@ const getSemanticColor = (
74
86
  };
75
87
 
76
88
  /**
77
- * Theme-aware icon component
78
- *
79
- * @example
80
- * <AtomicIcon name="heart-outline" size="md" color="primary" />
81
- * <AtomicIcon name="star" customSize={32} customColor="#FFD700" />
82
- * <AtomicIcon name="settings" size="lg" withBackground />
89
+ * Agnostic icon component - requires iconRenderer in DesignSystemProvider
83
90
  */
84
- export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(({
85
- name,
86
- size = "md",
87
- customSize,
88
- color,
89
- customColor,
90
- withBackground = false,
91
- backgroundColor,
92
- svgPath,
93
- svgViewBox = "0 0 24 24",
94
- accessibilityLabel,
95
- testID,
96
- style,
97
- }) => {
98
- const tokens = useAppDesignTokens();
99
-
100
- // Calculate size
101
- const baseSize = customSize ?? size;
102
- const iconSizesMap = tokens.iconSizes as Record<string, number>;
103
- const sizeInPixels: number = typeof baseSize === 'number'
104
- ? baseSize * tokens.spacingMultiplier
105
- : iconSizesMap[baseSize] ?? iconSizesMap['md'] ?? 24;
106
-
107
- // Calculate color
108
- const iconColor = customColor
109
- ? customColor
110
- : color
111
- ? getSemanticColor(color, tokens)
112
- : tokens.colors.textPrimary;
113
-
114
- // Validate icon - use fallback and log warning in DEV if invalid
115
- const isInvalidIcon = name && !(name in Ionicons.glyphMap);
116
- const iconName = name && !isInvalidIcon ? name : FALLBACK_ICON;
117
-
118
- if (__DEV__ && isInvalidIcon) {
119
- console.warn(`[DesignSystem] Invalid icon name: "${name}". Falling back to "${FALLBACK_ICON}"`);
120
- }
121
-
122
- const iconProps = {
123
- testID,
91
+ export const AtomicIcon: React.FC<AtomicIconProps> = React.memo(
92
+ ({
93
+ name,
94
+ size = "md",
95
+ customSize,
96
+ color,
97
+ customColor,
98
+ withBackground = false,
99
+ backgroundColor,
100
+ svgPath,
101
+ svgViewBox = "0 0 24 24",
124
102
  accessibilityLabel,
125
- style: [
126
- !svgPath && {
127
- padding: 4, // Prevent font truncation
128
- includeFontPadding: false, // Android specific
129
- },
130
- style,
131
- ] as StyleProp<ViewStyle>,
132
- };
133
-
134
- if (svgPath) {
135
- return (
136
- <Svg
137
- viewBox={svgViewBox}
138
- width={sizeInPixels}
139
- height={sizeInPixels}
140
- key="custom-svg-icon"
141
- {...iconProps}
142
- >
143
- <Path
144
- d={svgPath}
145
- fill={iconColor}
146
- />
147
- </Svg>
148
- );
149
- }
150
-
151
- const iconElement = (
152
- <Ionicons
153
- name={iconName as keyof typeof Ionicons.glyphMap}
154
- size={sizeInPixels}
155
- color={iconColor}
156
- {...iconProps}
157
- />
158
- );
159
-
160
- if (withBackground) {
161
- const bgColor = backgroundColor || tokens.colors.surfaceVariant;
162
- const containerSize = sizeInPixels + 16;
163
-
164
- return (
165
- <View
166
- style={[
167
- styles.backgroundContainer,
168
- {
169
- width: containerSize,
170
- height: containerSize,
171
- borderRadius: containerSize / 2,
172
- backgroundColor: bgColor,
173
- },
103
+ testID,
104
+ style,
105
+ }) => {
106
+ const tokens = useAppDesignTokens();
107
+ const iconRenderer = useIconRenderer();
108
+
109
+ // Calculate size
110
+ const baseSize = customSize ?? size;
111
+ const iconSizesMap = tokens.iconSizes as Record<string, number>;
112
+ const sizeInPixels: number =
113
+ typeof baseSize === "number"
114
+ ? baseSize * tokens.spacingMultiplier
115
+ : iconSizesMap[baseSize] ?? iconSizesMap["md"] ?? 24;
116
+
117
+ // Calculate color
118
+ const iconColor = customColor
119
+ ? customColor
120
+ : color
121
+ ? getSemanticColor(color, tokens)
122
+ : tokens.colors.textPrimary;
123
+
124
+ // SVG path rendering (built-in, no external dependency)
125
+ if (svgPath) {
126
+ const svgElement = (
127
+ <Svg
128
+ viewBox={svgViewBox}
129
+ width={sizeInPixels}
130
+ height={sizeInPixels}
131
+ key="custom-svg-icon"
132
+ testID={testID}
133
+ accessibilityLabel={accessibilityLabel}
134
+ >
135
+ <Path d={svgPath} fill={iconColor} />
136
+ </Svg>
137
+ );
138
+
139
+ if (withBackground) {
140
+ return renderWithBackground(
141
+ svgElement,
142
+ sizeInPixels,
143
+ backgroundColor || tokens.colors.surfaceVariant,
174
144
  style,
175
- ]}
176
- testID={testID}
177
- accessibilityLabel={accessibilityLabel}
178
- >
179
- {iconElement}
180
- </View>
181
- );
145
+ testID,
146
+ accessibilityLabel
147
+ );
148
+ }
149
+
150
+ return svgElement;
151
+ }
152
+
153
+ // No icon renderer provided - warn in dev and render nothing
154
+ if (!iconRenderer) {
155
+ if (__DEV__) {
156
+ console.warn(
157
+ "[DesignSystem] AtomicIcon requires an iconRenderer in DesignSystemProvider.\n" +
158
+ "Example:\n" +
159
+ "<DesignSystemProvider\n" +
160
+ ' iconRenderer={({ name, size, color }) => (\n' +
161
+ ' <YourIconLibrary name={name} size={size} color={color} />\n' +
162
+ " )}\n" +
163
+ ">"
164
+ );
165
+ }
166
+ return null;
167
+ }
168
+
169
+ // Build render props
170
+ const renderProps: IconRenderProps = {
171
+ name: name || "",
172
+ size: sizeInPixels,
173
+ color: iconColor,
174
+ style: style as StyleProp<ViewStyle>,
175
+ testID,
176
+ accessibilityLabel,
177
+ };
178
+
179
+ const iconElement = iconRenderer(renderProps);
180
+
181
+ if (withBackground) {
182
+ return renderWithBackground(
183
+ iconElement,
184
+ sizeInPixels,
185
+ backgroundColor || tokens.colors.surfaceVariant,
186
+ style,
187
+ testID,
188
+ accessibilityLabel
189
+ );
190
+ }
191
+
192
+ return <>{iconElement}</>;
182
193
  }
183
-
184
- return iconElement;
185
- });
194
+ );
186
195
 
187
196
  AtomicIcon.displayName = "AtomicIcon";
188
197
 
198
+ /**
199
+ * Helper to render icon with circular background
200
+ */
201
+ function renderWithBackground(
202
+ iconElement: React.ReactNode,
203
+ sizeInPixels: number,
204
+ bgColor: string,
205
+ style: StyleProp<ViewStyle> | undefined,
206
+ testID: string | undefined,
207
+ accessibilityLabel: string | undefined
208
+ ): React.ReactElement {
209
+ const containerSize = sizeInPixels + 16;
210
+
211
+ return (
212
+ <View
213
+ style={[
214
+ styles.backgroundContainer,
215
+ {
216
+ width: containerSize,
217
+ height: containerSize,
218
+ borderRadius: containerSize / 2,
219
+ backgroundColor: bgColor,
220
+ },
221
+ style,
222
+ ]}
223
+ testID={testID}
224
+ accessibilityLabel={accessibilityLabel}
225
+ >
226
+ {iconElement}
227
+ </View>
228
+ );
229
+ }
230
+
189
231
  const styles = StyleSheet.create({
190
232
  backgroundContainer: {
191
233
  justifyContent: "center",
192
234
  alignItems: "center",
193
235
  },
194
236
  });
195
-
196
-
@@ -1,26 +1,18 @@
1
1
  /**
2
2
  * Icon Type Definitions
3
- * Centralized icon types for @expo/vector-icons Ionicons
3
+ * Agnostic icon types - no dependency on any icon library
4
4
  */
5
5
 
6
- import type { Ionicons } from "@expo/vector-icons";
7
-
8
6
  /**
9
- * All available Ionicons names (type-safe)
7
+ * Icon name - just a string, interpreted by app's icon renderer
10
8
  */
11
- export type IoniconsName = keyof typeof Ionicons.glyphMap;
12
-
13
- /**
14
- * Icon name - either Ionicons or custom string
15
- */
16
- export type IconName = IoniconsName | string;
9
+ export type IconName = string;
17
10
 
18
11
  /**
19
12
  * Semantic icon size presets
20
13
  */
21
14
  export type IconSizePreset = "xs" | "sm" | "md" | "lg" | "xl" | "xxl";
22
15
 
23
-
24
16
  /**
25
17
  * Icon size - preset name or custom number in pixels
26
18
  */
@@ -50,25 +42,25 @@ export type IconColor =
50
42
  * Icon size mapping to pixels
51
43
  */
52
44
  export const ICON_SIZES: Record<IconSizePreset, number> = {
53
- xs: 12,
54
- sm: 16,
55
- md: 20,
56
- lg: 24,
57
- xl: 32,
58
- xxl: 48,
45
+ xs: 12,
46
+ sm: 16,
47
+ md: 20,
48
+ lg: 24,
49
+ xl: 32,
50
+ xxl: 48,
59
51
  } as const;
60
52
 
61
53
  /**
62
54
  * Get icon size in pixels
63
55
  */
64
56
  export function getIconSize(size: IconSize): number {
65
- if (typeof size === "number") return size;
66
- return ICON_SIZES[size];
57
+ if (typeof size === "number") return size;
58
+ return ICON_SIZES[size];
67
59
  }
68
60
 
69
61
  /**
70
62
  * Check if size is a preset
71
63
  */
72
64
  export function isIconSizePreset(size: IconSize): size is IconSizePreset {
73
- return typeof size === "string" && size in ICON_SIZES;
65
+ return typeof size === "string" && size in ICON_SIZES;
74
66
  }
@@ -14,7 +14,7 @@ import { InputHelper } from './input/components/InputHelper';
14
14
  *
15
15
  * Features:
16
16
  * - Pure React Native implementation (no Paper dependency)
17
- * - Ionicons for password toggle and custom icons
17
+ * - Icon support via AtomicIcon (app provides renderer)
18
18
  * - Outlined/filled/flat variants
19
19
  * - Error, success, disabled states
20
20
  * - Character counter
@@ -0,0 +1,102 @@
1
+ /**
2
+ * IconRegistry - Customizable Icon Rendering
3
+ *
4
+ * Allows apps to inject their own icon renderer instead of using
5
+ * the default Ionicons. This enables design system adoption without
6
+ * forcing a specific icon library.
7
+ *
8
+ * @example
9
+ * // App provides custom renderer
10
+ * import { MaterialIcons } from '@expo/vector-icons';
11
+ *
12
+ * const myRenderer = ({ name, size, color }) => (
13
+ * <MaterialIcons name={name} size={size} color={color} />
14
+ * );
15
+ *
16
+ * <DesignSystemProvider iconRenderer={myRenderer}>
17
+ * <App />
18
+ * </DesignSystemProvider>
19
+ */
20
+
21
+ import React, { createContext, useContext, ReactNode } from "react";
22
+ import type { StyleProp, ViewStyle } from "react-native";
23
+
24
+ /**
25
+ * Props passed to the icon renderer function
26
+ * These are generic and not tied to any specific icon library
27
+ */
28
+ export interface IconRenderProps {
29
+ /** Icon name - app interprets this based on their icon library */
30
+ name: string;
31
+ /** Size in pixels */
32
+ size: number;
33
+ /** Color (hex, rgba, named color, etc.) */
34
+ color: string;
35
+ /** Optional style */
36
+ style?: StyleProp<ViewStyle>;
37
+ /** Test ID for testing */
38
+ testID?: string;
39
+ /** Accessibility label */
40
+ accessibilityLabel?: string;
41
+ }
42
+
43
+ /**
44
+ * Icon renderer function type
45
+ * Apps provide this function to render icons with their preferred library
46
+ */
47
+ export type IconRenderer = (props: IconRenderProps) => ReactNode;
48
+
49
+ /**
50
+ * Internal registry store
51
+ */
52
+ interface IconRegistryValue {
53
+ renderIcon: IconRenderer;
54
+ }
55
+
56
+ const IconRegistryStore = createContext<IconRegistryValue | null>(null);
57
+
58
+ /**
59
+ * Icon provider props
60
+ */
61
+ interface IconProviderProps {
62
+ /** Icon renderer function */
63
+ renderIcon: IconRenderer;
64
+ /** Children */
65
+ children: ReactNode;
66
+ }
67
+
68
+ /**
69
+ * IconProvider - Provides custom icon renderer to the component tree
70
+ *
71
+ * @example
72
+ * <IconProvider renderIcon={myCustomRenderer}>
73
+ * <App />
74
+ * </IconProvider>
75
+ */
76
+ export const IconProvider: React.FC<IconProviderProps> = ({
77
+ renderIcon,
78
+ children,
79
+ }) => {
80
+ return (
81
+ <IconRegistryStore.Provider value={{ renderIcon }}>
82
+ {children}
83
+ </IconRegistryStore.Provider>
84
+ );
85
+ };
86
+
87
+ /**
88
+ * Hook to access the icon renderer
89
+ * Returns null if no custom renderer is provided (fallback to Ionicons)
90
+ */
91
+ export const useIconRenderer = (): IconRenderer | null => {
92
+ const registry = useContext(IconRegistryStore);
93
+ return registry?.renderIcon ?? null;
94
+ };
95
+
96
+ /**
97
+ * Hook to check if a custom icon renderer is available
98
+ */
99
+ export const useHasCustomIconRenderer = (): boolean => {
100
+ const registry = useContext(IconRegistryStore);
101
+ return registry !== null;
102
+ };
@@ -40,8 +40,17 @@ export {
40
40
  type IconSize,
41
41
  type IconColor,
42
42
  type IconName,
43
+ type IconRenderProps,
43
44
  } from './AtomicIcon';
44
45
 
46
+ // Icon Registry (for custom icon renderers)
47
+ export {
48
+ IconProvider,
49
+ useIconRenderer,
50
+ useHasCustomIconRenderer,
51
+ type IconRenderer,
52
+ } from './IconRegistry';
53
+
45
54
 
46
55
  // Avatar
47
56
  export { AtomicAvatar, type AtomicAvatarProps } from './AtomicAvatar';
@@ -16,7 +16,7 @@ import {
16
16
  } from "../../../theme";
17
17
 
18
18
  export interface ExceptionErrorStateProps {
19
- /** Icon name from Ionicons */
19
+ /** Icon name (interpreted by app's icon renderer) */
20
20
  icon?: string;
21
21
  /** Error title */
22
22
  title: string;
@@ -77,6 +77,9 @@ export { useCommonStyles } from './hooks/useCommonStyles';
77
77
 
78
78
  export { DesignSystemProvider } from './infrastructure/providers/DesignSystemProvider';
79
79
 
80
+ // Re-export icon types for convenience
81
+ export type { IconRenderer, IconRenderProps } from '../atoms/IconRegistry';
82
+
80
83
  // =============================================================================
81
84
  // THEME OBJECTS
82
85
  // =============================================================================
@@ -8,6 +8,7 @@ import { useDesignSystemTheme } from '../globalThemeStore';
8
8
  import type { CustomThemeColors } from '../../core/CustomColors';
9
9
  import { SplashScreen } from '../../../molecules/splash';
10
10
  import type { SplashScreenProps } from '../../../molecules/splash/types';
11
+ import { IconProvider, type IconRenderer } from '../../../atoms/IconRegistry';
11
12
 
12
13
 
13
14
  interface DesignSystemProviderProps {
@@ -16,7 +17,7 @@ interface DesignSystemProviderProps {
16
17
  /** Custom theme colors to override defaults */
17
18
  customColors?: CustomThemeColors;
18
19
  /** Custom fonts to load (name -> source map) */
19
- fonts?: Record<string, any>;
20
+ fonts?: Record<string, any>;
20
21
  /** Show loading indicator while initializing (default: true) */
21
22
  showLoadingIndicator?: boolean;
22
23
  /** Splash screen configuration (used when showLoadingIndicator is true) */
@@ -27,6 +28,15 @@ interface DesignSystemProviderProps {
27
28
  onInitialized?: () => void;
28
29
  /** Callback when initialization fails */
29
30
  onError?: (error: unknown) => void;
31
+ /**
32
+ * Custom icon renderer function
33
+ * Allows apps to use their own icon library instead of Ionicons
34
+ * @example
35
+ * iconRenderer={({ name, size, color }) => (
36
+ * <MaterialIcons name={name} size={size} color={color} />
37
+ * )}
38
+ */
39
+ iconRenderer?: IconRenderer;
30
40
  }
31
41
 
32
42
  /**
@@ -42,6 +52,7 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
42
52
  loadingComponent,
43
53
  onInitialized,
44
54
  onError,
55
+ iconRenderer,
45
56
  }: DesignSystemProviderProps) => {
46
57
  const [isInitialized, setIsInitialized] = useState(false);
47
58
 
@@ -104,10 +115,17 @@ export const DesignSystemProvider: React.FC<DesignSystemProviderProps> = ({
104
115
  content = children;
105
116
  }
106
117
 
118
+ // Wrap with IconProvider if custom renderer provided
119
+ const wrappedContent = iconRenderer ? (
120
+ <IconProvider renderIcon={iconRenderer}>{content}</IconProvider>
121
+ ) : (
122
+ content
123
+ );
124
+
107
125
  return (
108
126
  <GestureHandlerRootView style={{ flex: 1 }}>
109
127
  <SafeAreaProvider initialMetrics={initialWindowMetrics}>
110
- {content}
128
+ {wrappedContent}
111
129
  </SafeAreaProvider>
112
130
  </GestureHandlerRootView>
113
131
  );