@nwartz/design-system 0.1.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.
@@ -0,0 +1,410 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ View,
6
+ type ViewStyle,
7
+ type StyleProp,
8
+ } from 'react-native';
9
+
10
+ import { spacing } from '../tokens/spacing';
11
+ import { elevation } from '../tokens/elevation';
12
+ import { Icon } from './Icon';
13
+ import { Text } from './Text';
14
+
15
+ export interface DatePickerProps {
16
+ label?: string;
17
+ startDate?: Date | null;
18
+ endDate?: Date | null;
19
+ onChange?: (start: Date | null, end: Date | null) => void;
20
+ disabled?: boolean;
21
+ errorMessage?: string;
22
+ placeholder?: string;
23
+ open?: boolean;
24
+ onToggle?: () => void;
25
+ }
26
+
27
+ const WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
28
+ const MONTH_NAMES = [
29
+ 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
30
+ 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro',
31
+ ];
32
+
33
+ function getDaysInMonth(year: number, month: number): number {
34
+ return new Date(year, month + 1, 0).getDate();
35
+ }
36
+
37
+ function getFirstDayOfMonth(year: number, month: number): number {
38
+ const day = new Date(year, month, 1).getDay();
39
+
40
+ return day === 0 ? 6 : day - 1;
41
+ }
42
+
43
+ function formatDate(date: Date): string {
44
+ const day = date.getDate().toString().padStart(2, '0');
45
+ const month = (date.getMonth() + 1).toString().padStart(2, '0');
46
+ const year = date.getFullYear();
47
+
48
+ return `${day}/${month}/${year}`;
49
+ }
50
+
51
+ function isSameDay(a: Date, b: Date): boolean {
52
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
53
+ }
54
+
55
+ function isInRange(date: Date, start: Date | null, end: Date | null): boolean {
56
+ if (!start || !end) {
57
+ return false;
58
+ }
59
+
60
+ const time = date.getTime();
61
+
62
+ return time > start.getTime() && time < end.getTime();
63
+ }
64
+
65
+ /**
66
+ * Date range picker with calendar grid, month navigation, input fields, and action buttons.
67
+ *
68
+ * ```tsx
69
+ * <DatePicker startDate={start} endDate={end} onChange={setDates} />
70
+ * ```
71
+ */
72
+ export function DatePicker({
73
+ startDate,
74
+ endDate,
75
+ onChange,
76
+ disabled = false,
77
+ errorMessage,
78
+ placeholder = 'Selecione as datas',
79
+ open = false,
80
+ onToggle,
81
+ }: DatePickerProps) {
82
+ const [isHovered, setIsHovered] = useState(false);
83
+ const [viewYear, setViewYear] = useState(startDate?.getFullYear() ?? new Date().getFullYear());
84
+ const [viewMonth, setViewMonth] = useState(startDate?.getMonth() ?? new Date().getMonth());
85
+ const [selectingEnd, setSelectingEnd] = useState(false);
86
+
87
+ const showError = !!errorMessage;
88
+ const today = new Date();
89
+
90
+ const navigateMonth = (delta: number) => {
91
+ const newDate = new Date(viewYear, viewMonth + delta, 1);
92
+
93
+ setViewYear(newDate.getFullYear());
94
+ setViewMonth(newDate.getMonth());
95
+ };
96
+
97
+ const handleDayPress = (day: number) => {
98
+ const date = new Date(viewYear, viewMonth, day);
99
+
100
+ if (!selectingEnd) {
101
+ onChange?.(date, null);
102
+ setSelectingEnd(true);
103
+ } else {
104
+ if (startDate && date < startDate) {
105
+ onChange?.(date, startDate);
106
+ } else {
107
+ onChange?.(startDate ?? date, date);
108
+ }
109
+
110
+ setSelectingEnd(false);
111
+ }
112
+ };
113
+
114
+ const handleCancel = () => {
115
+ onChange?.(null, null);
116
+ setSelectingEnd(false);
117
+ onToggle?.();
118
+ };
119
+
120
+ const handleApply = () => {
121
+ onToggle?.();
122
+ };
123
+
124
+ const daysInMonth = getDaysInMonth(viewYear, viewMonth);
125
+ const firstDay = getFirstDayOfMonth(viewYear, viewMonth);
126
+
127
+ const triggerStyle: StyleProp<ViewStyle> = [
128
+ styles.trigger,
129
+ {
130
+ backgroundColor: '#faf9f7',
131
+ borderColor: showError ? '#bb4355' : '#d9d8d7',
132
+ borderWidth: 1,
133
+ borderRadius: 8,
134
+ },
135
+ elevation.xs,
136
+ ];
137
+
138
+ return (
139
+ <View style={styles.wrapper}>
140
+ <Pressable
141
+ disabled={disabled}
142
+ onPress={onToggle}
143
+ onHoverIn={() => setIsHovered(true)}
144
+ onHoverOut={() => setIsHovered(false)}
145
+ style={[
146
+ triggerStyle,
147
+ isHovered && !disabled && { backgroundColor: '#e9e8e7' },
148
+ ]}>
149
+ <Icon name="timer" size={20} color="#101828" />
150
+ <Text
151
+ variant="body"
152
+ textStyle={[
153
+ styles.triggerText,
154
+ {
155
+ color: startDate ? '#101828' : '#8e8d8b',
156
+ fontWeight: startDate ? '600' : '400',
157
+ },
158
+ ]}>
159
+ {(() => {
160
+ if (startDate && endDate) {
161
+ return `${formatDate(startDate)} -- ${formatDate(endDate)}`;
162
+ }
163
+
164
+ if (startDate) {
165
+ return `${formatDate(startDate)} --`;
166
+ }
167
+
168
+ return placeholder;
169
+ })()}
170
+ </Text>
171
+ </Pressable>
172
+
173
+ {showError && (
174
+ <View style={styles.errorRow}>
175
+ <Icon name="warning-circle" size={16} color="#bb4355" />
176
+ <Text variant="body" textStyle={[styles.errorText, { color: '#737271' }]}>
177
+ {errorMessage}
178
+ </Text>
179
+ </View>
180
+ )}
181
+
182
+ {open && (
183
+ <View
184
+ style={[
185
+ styles.calendar,
186
+ {
187
+ backgroundColor: '#f6f5f4',
188
+ borderColor: '#e9e8e7',
189
+ },
190
+ elevation.lg,
191
+ ]}>
192
+ <View style={styles.contentArea}>
193
+ <View style={styles.inputRow}>
194
+ <View style={[styles.dateInput, { borderColor: '#d9d8d7' }]}>
195
+ <Text variant="body" textStyle={{ color: startDate ? '#141414' : '#8e8d8b', fontSize: 14 }}>
196
+ {startDate ? formatDate(startDate) : 'Data inicial'}
197
+ </Text>
198
+ </View>
199
+ <Text variant="body" textStyle={{ color: '#d9d8d7', fontSize: 16 }}>--</Text>
200
+ <View style={[styles.dateInput, { borderColor: '#d0d5dd' }]}>
201
+ <Text variant="body" textStyle={{ color: endDate ? '#101828' : '#8e8d8b', fontSize: 14 }}>
202
+ {endDate ? formatDate(endDate) : 'Data final'}
203
+ </Text>
204
+ </View>
205
+ </View>
206
+
207
+ <View style={styles.header}>
208
+ <Pressable onPress={() => navigateMonth(-1)} hitSlop={8} style={styles.navButton}>
209
+ <View style={{ transform: [{ rotate: '180deg' }] }}>
210
+ <Icon name="caret-right" size={16} color="#737271" />
211
+ </View>
212
+ </Pressable>
213
+
214
+ <Text variant="body" textStyle={styles.monthLabel}>
215
+ {MONTH_NAMES[viewMonth]} {viewYear}
216
+ </Text>
217
+
218
+ <Pressable onPress={() => navigateMonth(1)} hitSlop={8} style={styles.navButton}>
219
+ <Icon name="caret-right" size={16} color="#737271" />
220
+ </Pressable>
221
+ </View>
222
+
223
+ <View style={styles.weekdayRow}>
224
+ {WEEKDAYS.map((day) => (
225
+ <Text key={day} variant="caption" textStyle={styles.weekdayText}>
226
+ {day}
227
+ </Text>
228
+ ))}
229
+ </View>
230
+
231
+ <View style={styles.daysGrid}>
232
+ {Array.from({ length: firstDay }).map((_, i) => (
233
+ <View key={`empty-${i}`} style={styles.dayCell} />
234
+ ))}
235
+
236
+ {Array.from({ length: daysInMonth }).map((_, i) => {
237
+ const day = i + 1;
238
+ const date = new Date(viewYear, viewMonth, day);
239
+ const isStartDate = !!startDate && isSameDay(date, startDate);
240
+ const isEndDate = !!endDate && isSameDay(date, endDate);
241
+ const isSelected = isStartDate || isEndDate;
242
+ const inRange = isInRange(date, startDate ?? null, endDate ?? null);
243
+ const isTodayDate = isSameDay(date, today);
244
+
245
+ return (
246
+ <Pressable
247
+ key={day}
248
+ onPress={() => handleDayPress(day)}
249
+ style={[
250
+ styles.dayCell,
251
+ inRange && { backgroundColor: '#d9d8d7' },
252
+ (isStartDate || (!endDate && selectingEnd && startDate && isSameDay(date, startDate))) && {
253
+ borderRadius: 20,
254
+ backgroundColor: '#cefe00',
255
+ },
256
+ isEndDate && {
257
+ borderRadius: 20,
258
+ backgroundColor: '#cefe00',
259
+ },
260
+ ]}>
261
+ <Text
262
+ variant="caption"
263
+ textStyle={[
264
+ styles.dayText,
265
+ {
266
+ color: isSelected ? '#000000' : '#141414',
267
+ fontWeight: isTodayDate || isSelected ? '500' : '400',
268
+ },
269
+ ]}>
270
+ {day}
271
+ </Text>
272
+ </Pressable>
273
+ );
274
+ })}
275
+ </View>
276
+ </View>
277
+
278
+ <View style={styles.bottomPanel}>
279
+ <Pressable
280
+ onPress={handleCancel}
281
+ style={[styles.actionButton, { borderColor: '#d9d8d7', backgroundColor: '#faf9f7' }]}>
282
+ <Text variant="body" textStyle={{ color: '#101828', fontSize: 14, fontWeight: '600' }}>
283
+ Cancelar
284
+ </Text>
285
+ </Pressable>
286
+
287
+ <Pressable
288
+ onPress={handleApply}
289
+ style={[styles.actionButton, { borderColor: '#cefe00', backgroundColor: '#cefe00' }]}>
290
+ <Text variant="body" textStyle={{ color: '#000000', fontSize: 14, fontWeight: '600' }}>
291
+ Aplicar
292
+ </Text>
293
+ </Pressable>
294
+ </View>
295
+ </View>
296
+ )}
297
+ </View>
298
+ );
299
+ }
300
+
301
+ const styles = StyleSheet.create({
302
+ wrapper: {
303
+ gap: spacing.xs,
304
+ },
305
+ trigger: {
306
+ flexDirection: 'row',
307
+ alignItems: 'center',
308
+ height: 40,
309
+ paddingHorizontal: 16,
310
+ gap: 8,
311
+ },
312
+ triggerText: {
313
+ fontSize: 14,
314
+ lineHeight: 21,
315
+ },
316
+ errorRow: {
317
+ flexDirection: 'row',
318
+ alignItems: 'center',
319
+ gap: spacing.xs,
320
+ },
321
+ errorText: {
322
+ fontSize: 14,
323
+ lineHeight: 21,
324
+ },
325
+ calendar: {
326
+ borderRadius: 12,
327
+ borderWidth: 1,
328
+ overflow: 'hidden',
329
+ },
330
+ contentArea: {
331
+ padding: 20,
332
+ gap: 16,
333
+ },
334
+ inputRow: {
335
+ flexDirection: 'row',
336
+ alignItems: 'center',
337
+ gap: 8,
338
+ },
339
+ dateInput: {
340
+ flex: 1,
341
+ height: 44,
342
+ borderWidth: 1,
343
+ borderRadius: 12,
344
+ backgroundColor: '#faf9f7',
345
+ paddingHorizontal: 14,
346
+ alignItems: 'flex-start',
347
+ justifyContent: 'center',
348
+ },
349
+ header: {
350
+ flexDirection: 'row',
351
+ alignItems: 'center',
352
+ justifyContent: 'space-between',
353
+ },
354
+ navButton: {
355
+ width: 36,
356
+ height: 36,
357
+ borderRadius: 16,
358
+ borderWidth: 1,
359
+ borderColor: '#d9d8d7',
360
+ alignItems: 'center',
361
+ justifyContent: 'center',
362
+ },
363
+ monthLabel: {
364
+ fontSize: 16,
365
+ fontWeight: '600',
366
+ lineHeight: 24,
367
+ color: '#141414',
368
+ },
369
+ weekdayRow: {
370
+ flexDirection: 'row',
371
+ },
372
+ weekdayText: {
373
+ flex: 1,
374
+ textAlign: 'center',
375
+ fontSize: 14,
376
+ lineHeight: 20,
377
+ fontWeight: '500',
378
+ color: '#101828',
379
+ },
380
+ daysGrid: {
381
+ flexDirection: 'row',
382
+ flexWrap: 'wrap',
383
+ },
384
+ dayCell: {
385
+ width: '14.28%',
386
+ aspectRatio: 1,
387
+ alignItems: 'center',
388
+ justifyContent: 'center',
389
+ borderRadius: 20,
390
+ },
391
+ dayText: {
392
+ fontSize: 14,
393
+ lineHeight: 20,
394
+ },
395
+ bottomPanel: {
396
+ flexDirection: 'row',
397
+ padding: 16,
398
+ gap: 12,
399
+ borderTopWidth: 1,
400
+ borderTopColor: '#e9e8e7',
401
+ },
402
+ actionButton: {
403
+ flex: 1,
404
+ height: 40,
405
+ borderRadius: 16,
406
+ borderWidth: 1,
407
+ alignItems: 'center',
408
+ justifyContent: 'center',
409
+ },
410
+ });
@@ -0,0 +1,158 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ FlatList,
4
+ Pressable,
5
+ StyleSheet,
6
+ View,
7
+ type ViewProps,
8
+ } from 'react-native';
9
+
10
+ import { useTheme } from '../theme';
11
+ import { radius } from '../tokens/radius';
12
+ import { spacing } from '../tokens/spacing';
13
+ import { elevation } from '../tokens/elevation';
14
+ import { Icon } from './Icon';
15
+ import { Text } from './Text';
16
+
17
+ export interface DropdownItem {
18
+ key: string;
19
+ label: string;
20
+ disabled?: boolean;
21
+ }
22
+
23
+ export interface DropdownSelectProps extends ViewProps {
24
+ /** List of items to display. */
25
+ items: DropdownItem[];
26
+ /** Currently selected item key. */
27
+ value?: string | null;
28
+ /** Called when an item is selected. */
29
+ onSelect?: (item: DropdownItem) => void;
30
+ /** Show the dropdown list. */
31
+ open?: boolean;
32
+ /** Max visible items before scroll. */
33
+ maxVisible?: number;
34
+ }
35
+
36
+ /**
37
+ * Dropdown select list with check indicator and hover/focus states.
38
+ *
39
+ * ```tsx
40
+ * <DropdownSelect
41
+ * items={[{ key: '1', label: 'Option 1' }]}
42
+ * value="1"
43
+ * onSelect={handleSelect}
44
+ * />
45
+ * ```
46
+ */
47
+ export function DropdownSelect({
48
+ items,
49
+ value,
50
+ onSelect,
51
+ open = true,
52
+ maxVisible = 5,
53
+ style,
54
+ ...rest
55
+ }: DropdownSelectProps) {
56
+ const theme = useTheme();
57
+ const [hoveredKey, setHoveredKey] = useState<string | null>(null);
58
+
59
+ if (!open) return null;
60
+
61
+ return (
62
+ <View
63
+ style={[
64
+ styles.container,
65
+ {
66
+ backgroundColor: theme.colors.backgroundScreen,
67
+ borderColor: theme.colors.shapeSecondary,
68
+ },
69
+ elevation.xl,
70
+ style,
71
+ ]}
72
+ {...rest}>
73
+ <FlatList
74
+ data={items}
75
+ keyExtractor={(item) => item.key}
76
+ bounces={false}
77
+ showsVerticalScrollIndicator={items.length > maxVisible}
78
+ nestedScrollEnabled
79
+ renderItem={({ item }) => {
80
+ const isSelected = item.key === value;
81
+ const isHovered = item.key === hoveredKey;
82
+ const isDisabled = item.disabled;
83
+
84
+ let itemBg: string;
85
+
86
+ if (isHovered) {
87
+ itemBg = theme.colors.shapeSecondary;
88
+ } else if (isSelected) {
89
+ itemBg = theme.colors.backgroundScreen;
90
+ } else {
91
+ itemBg = 'transparent';
92
+ }
93
+
94
+ return (
95
+ <View>
96
+ <Pressable
97
+ disabled={isDisabled}
98
+ onPress={() => onSelect?.(item)}
99
+ onHoverIn={() => setHoveredKey(item.key)}
100
+ onHoverOut={() => setHoveredKey(null)}
101
+ style={() => [
102
+ styles.item,
103
+ { backgroundColor: itemBg },
104
+ isDisabled && styles.disabled,
105
+ ]}>
106
+ <Text
107
+ variant="body"
108
+ color="textSecondary"
109
+ textStyle={[
110
+ styles.itemLabel,
111
+ { color: isDisabled ? theme.colors.textTertiary : theme.colors.textSecondary },
112
+ ]}>
113
+ {item.label}
114
+ </Text>
115
+ {isSelected && (
116
+ <Icon name="check" size={16} color={theme.colors.textSecondary} />
117
+ )}
118
+ </Pressable>
119
+ {item.key !== items[items.length - 1]?.key && (
120
+ <View
121
+ style={[styles.divider, { backgroundColor: theme.colors.shapePrimary }]}
122
+ />
123
+ )}
124
+ </View>
125
+ );
126
+ }}
127
+ />
128
+ </View>
129
+ );
130
+ }
131
+
132
+ const styles = StyleSheet.create({
133
+ container: {
134
+ borderRadius: radius.sm,
135
+ borderWidth: 1,
136
+ padding: spacing.xs,
137
+ gap: spacing.xxs,
138
+ },
139
+ item: {
140
+ flexDirection: 'row',
141
+ alignItems: 'center',
142
+ justifyContent: 'space-between',
143
+ paddingHorizontal: spacing.lg,
144
+ height: 40,
145
+ borderRadius: radius.sm,
146
+ },
147
+ disabled: {
148
+ opacity: 0.32,
149
+ },
150
+ itemLabel: {
151
+ fontSize: 14,
152
+ lineHeight: 21,
153
+ },
154
+ divider: {
155
+ height: 1,
156
+ marginHorizontal: spacing.lg,
157
+ },
158
+ });
@@ -0,0 +1,131 @@
1
+ import {
2
+ CaretDown as CaretDownIcon,
3
+ CaretUp as CaretUpIcon,
4
+ CaretRight as CaretRightIcon,
5
+ Check as CheckIcon,
6
+ XCircle as XCircleIcon,
7
+ X as XIcon,
8
+ WarningCircle as WarningCircleIcon,
9
+ WarningOctagon as WarningOctagonIcon,
10
+ House as HouseIcon,
11
+ Bell as BellIcon,
12
+ Broadcast as BroadcastIcon,
13
+ ChatCircle as ChatCircleIcon,
14
+ CheckCircle as CheckCircleIcon,
15
+ Truck as TruckIcon,
16
+ CreditCard as CreditCardIcon,
17
+ Crown as CrownIcon,
18
+ CrosshairSimple as CrosshairSimpleIcon,
19
+ Gauge as GaugeIcon,
20
+ Lightning as LightningIcon,
21
+ ThumbsUp as ThumbsUpIcon,
22
+ MapTrifold as MapTrifoldIcon,
23
+ MapPin as MapPinIcon,
24
+ Medal as MedalIcon,
25
+ Microphone as MicrophoneIcon,
26
+ SpeakerSimpleX as SpeakerSimpleXIcon,
27
+ Play as PlayIcon,
28
+ Plus as PlusIcon,
29
+ RadioButton as RadioButtonIcon,
30
+ Radio as RadioIcon,
31
+ ChartBar as ChartBarIcon,
32
+ Receipt as ReceiptIcon,
33
+ PathIcon,
34
+ MagnifyingGlass as MagnifyingGlassIcon,
35
+ GearSix as GearSixIcon,
36
+ ShareNetwork as ShareNetworkIcon,
37
+ Star as StarIcon,
38
+ SignOut as SignOutIcon,
39
+ SteeringWheel as SteeringWheelIcon,
40
+ Timer as TimerIcon,
41
+ Pencil as PencilIcon,
42
+ User as UserIcon,
43
+ UserMinus as UserMinusIcon,
44
+ Users as UsersIcon,
45
+ Globe as GlobeIcon,
46
+ Wallet as WalletIcon,
47
+ Lock as LockIcon,
48
+ } from "phosphor-react-native";
49
+
50
+ /**
51
+ * Icon name registry. Maps a string name to a Phosphor icon component.
52
+ * Add new icons here as they're needed across the design system.
53
+ */
54
+ export const iconMap = {
55
+ "caret-down": CaretDownIcon,
56
+ "caret-up": CaretUpIcon,
57
+ "caret-right": CaretRightIcon,
58
+ check: CheckIcon,
59
+ "x-circle": XCircleIcon,
60
+ x: XIcon,
61
+ "warning-circle": WarningCircleIcon,
62
+ "warning-octagon": WarningOctagonIcon,
63
+ house: HouseIcon,
64
+ bell: BellIcon,
65
+ broadcast: BroadcastIcon,
66
+ "chat-circle": ChatCircleIcon,
67
+ "check-circle": CheckCircleIcon,
68
+ truck: TruckIcon,
69
+ "credit-card": CreditCardIcon,
70
+ crown: CrownIcon,
71
+ "crosshair-simple": CrosshairSimpleIcon,
72
+ gauge: GaugeIcon,
73
+ lightning: LightningIcon,
74
+ "thumbs-up": ThumbsUpIcon,
75
+ "map-trifold": MapTrifoldIcon,
76
+ "map-pin": MapPinIcon,
77
+ medal: MedalIcon,
78
+ microphone: MicrophoneIcon,
79
+ "speaker-simple-x": SpeakerSimpleXIcon,
80
+ play: PlayIcon,
81
+ plus: PlusIcon,
82
+ "radio-button": RadioButtonIcon,
83
+ radio: RadioIcon,
84
+ "chart-bar": ChartBarIcon,
85
+ receipt: ReceiptIcon,
86
+ path: PathIcon,
87
+ "magnifying-glass": MagnifyingGlassIcon,
88
+ "gear-six": GearSixIcon,
89
+ "share-network": ShareNetworkIcon,
90
+ star: StarIcon,
91
+ "sign-out": SignOutIcon,
92
+ "steering-wheel": SteeringWheelIcon,
93
+ timer: TimerIcon,
94
+ pencil: PencilIcon,
95
+ user: UserIcon,
96
+ "user-minus": UserMinusIcon,
97
+ users: UsersIcon,
98
+ globe: GlobeIcon,
99
+ wallet: WalletIcon,
100
+ lock: LockIcon,
101
+ } as const;
102
+
103
+ export type IconName = keyof typeof iconMap;
104
+
105
+ export interface IconProps {
106
+ /** Icon name from the registry. */
107
+ name: IconName;
108
+ /** Icon size in pixels. Defaults to 16. */
109
+ size?: number;
110
+ /** Icon color. */
111
+ color?: string;
112
+ /** Icon weight / stroke thickness. */
113
+ weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone";
114
+ }
115
+
116
+ /**
117
+ * Design-system icon component backed by Phosphor.
118
+ *
119
+ * ```tsx
120
+ * <Icon name="caret-down" size={16} color="#737271" />
121
+ * ```
122
+ */
123
+ export function Icon({ name, size = 16, color, weight = "regular" }: IconProps) {
124
+ const IconComponent = iconMap[name];
125
+
126
+ if (!IconComponent) {
127
+ return null;
128
+ }
129
+
130
+ return <IconComponent size={size} color={color} weight={weight} />;
131
+ }