@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 +1 -3
- package/src/atoms/AtomicBadge.tsx +1 -1
- package/src/atoms/AtomicIcon.README.md +121 -252
- package/src/atoms/AtomicIcon.tsx +160 -120
- package/src/atoms/AtomicIcon.types.ts +12 -20
- package/src/atoms/AtomicInput.tsx +1 -1
- package/src/atoms/IconRegistry.tsx +102 -0
- package/src/atoms/index.ts +9 -0
- package/src/exception/presentation/components/ExceptionErrorState.tsx +1 -1
- package/src/theme/index.ts +3 -0
- package/src/theme/infrastructure/providers/DesignSystemProvider.tsx +20 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
64
|
+
## Size Presets
|
|
55
65
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
75
|
+
## Semantic Colors
|
|
63
76
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
##
|
|
88
|
+
## Examples
|
|
69
89
|
|
|
70
|
-
|
|
90
|
+
### Different Icon Libraries
|
|
71
91
|
|
|
72
92
|
```tsx
|
|
73
|
-
//
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
//
|
|
97
|
-
<
|
|
98
|
-
name
|
|
99
|
-
|
|
100
|
+
// Material Icons
|
|
101
|
+
<DesignSystemProvider
|
|
102
|
+
iconRenderer={({ name, size, color }) => (
|
|
103
|
+
<MaterialIcons name={name} size={size} color={color} />
|
|
104
|
+
)}
|
|
100
105
|
/>
|
|
101
106
|
|
|
102
|
-
//
|
|
103
|
-
<
|
|
104
|
-
name
|
|
105
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
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
|
-
|
|
179
|
-
backgroundColor="#d4edda"
|
|
130
|
+
backgroundColor="#E3F2FD"
|
|
180
131
|
/>
|
|
181
132
|
```
|
|
182
133
|
|
|
183
|
-
|
|
134
|
+
### Custom SVG Path (No Renderer Needed)
|
|
135
|
+
|
|
184
136
|
```tsx
|
|
185
|
-
// ❌ Unnecessary background
|
|
186
137
|
<AtomicIcon
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
##
|
|
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="
|
|
274
|
-
|
|
275
|
-
color="success"
|
|
276
|
-
accessibilityLabel="Completed"
|
|
148
|
+
name="menu"
|
|
149
|
+
accessibilityLabel="Open navigation menu"
|
|
277
150
|
/>
|
|
278
151
|
```
|
|
279
152
|
|
|
280
|
-
##
|
|
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
|
-
|
|
155
|
+
If you were using the old Ionicons-specific AtomicIcon:
|
|
296
156
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
161
|
+
```tsx
|
|
162
|
+
// Before (implicit Ionicons)
|
|
163
|
+
<AtomicIcon name="heart" />
|
|
303
164
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
##
|
|
175
|
+
## Performance
|
|
309
176
|
|
|
310
|
-
-
|
|
311
|
-
-
|
|
312
|
-
-
|
|
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
|
-
##
|
|
181
|
+
## Related
|
|
315
182
|
|
|
316
|
-
|
|
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
|
package/src/atoms/AtomicIcon.tsx
CHANGED
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AtomicIcon -
|
|
2
|
+
* AtomicIcon - Agnostic Icon Component
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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
|
|
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
|
|
27
|
-
name?:
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
7
|
+
* Icon name - just a string, interpreted by app's icon renderer
|
|
10
8
|
*/
|
|
11
|
-
export type
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
* -
|
|
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
|
+
};
|
package/src/atoms/index.ts
CHANGED
|
@@ -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';
|
package/src/theme/index.ts
CHANGED
|
@@ -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
|
-
{
|
|
128
|
+
{wrappedContent}
|
|
111
129
|
</SafeAreaProvider>
|
|
112
130
|
</GestureHandlerRootView>
|
|
113
131
|
);
|