@timmbr/native-ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/native/index.tsx +324 -104
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmbr/native-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Unbundled Gluestack UI v2 primitives with shared Timmbr design tokens for React Native and Web",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,8 +34,8 @@
34
34
  "dependencies": {
35
35
  "class-variance-authority": "^0.7.1",
36
36
  "@timmbr/icons": "1.7.0",
37
- "@timmbr/theme": "1.3.0",
38
- "@timmbr/utils": "1.6.0"
37
+ "@timmbr/utils": "1.6.0",
38
+ "@timmbr/theme": "1.3.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18.0.0",
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useState, useMemo } from 'react';
1
+ import React, { createContext, useCallback, useContext, useState, useMemo } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text as RNText,
@@ -65,16 +65,16 @@ export const TimmbrNativeProvider: React.FC<TimmbrNativeProviderProps> = ({
65
65
  return currentMode;
66
66
  }, [currentMode, systemScheme]);
67
67
 
68
- const setMode = (newMode: ThemeMode) => {
68
+ const setMode = useCallback((newMode: ThemeMode) => {
69
69
  setUncontrolledMode(newMode);
70
- };
70
+ }, []);
71
71
 
72
- const toggleTheme = () => {
72
+ const toggleTheme = useCallback(() => {
73
73
  setUncontrolledMode((prev) => {
74
74
  const active = prev === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : prev;
75
75
  return active === 'dark' ? 'light' : 'dark';
76
76
  });
77
- };
77
+ }, [systemScheme]);
78
78
 
79
79
  const contextValue = useMemo(
80
80
  () => ({
@@ -83,7 +83,7 @@ export const TimmbrNativeProvider: React.FC<TimmbrNativeProviderProps> = ({
83
83
  setMode,
84
84
  toggleTheme,
85
85
  }),
86
- [currentMode, resolvedTheme]
86
+ [currentMode, resolvedTheme, setMode, toggleTheme]
87
87
  );
88
88
 
89
89
  return (
@@ -193,7 +193,7 @@ const headingSizeMap: Record<string, string> = {
193
193
  };
194
194
 
195
195
  export const Heading: React.FC<HeadingProps> = ({
196
- size = 'lg',
196
+ size = 'lg' as NonNullable<HeadingProps['size']>,
197
197
  className = '',
198
198
  children,
199
199
  ...props
@@ -208,11 +208,35 @@ export const Heading: React.FC<HeadingProps> = ({
208
208
  );
209
209
  };
210
210
 
211
+ const NON_COLOR_TEXT_UTILITY =
212
+ /^(?:dark:)?text-(xs|sm|base|lg|xl|2xl|3xl|4xl|left|center|right|justify|auto|\[\d+(px|rem|em)?\])$/;
213
+
214
+ function hasExplicitTextColorClass(className: string): boolean {
215
+ return className
216
+ .split(/\s+/)
217
+ .filter(Boolean)
218
+ .some((token) => /^(?:dark:)?text-/.test(token) && !NON_COLOR_TEXT_UTILITY.test(token));
219
+ }
220
+
221
+ export type ButtonVariant = 'solid' | 'outline' | 'link';
222
+ export type ButtonAction = 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
223
+ export type ButtonSize = 'sm' | 'md' | 'lg';
224
+
225
+ interface ButtonStyleContextValue {
226
+ variant: ButtonVariant;
227
+ action: ButtonAction;
228
+ }
229
+
230
+ const ButtonStyleContext = createContext<ButtonStyleContextValue>({
231
+ variant: 'solid',
232
+ action: 'primary',
233
+ });
234
+
211
235
  export interface ButtonProps extends Omit<PressableProps, 'children'> {
212
236
  className?: string;
213
- variant?: 'solid' | 'outline' | 'link';
214
- action?: 'primary' | 'secondary' | 'positive' | 'negative' | 'default';
215
- size?: 'sm' | 'md' | 'lg';
237
+ variant?: ButtonVariant;
238
+ action?: ButtonAction;
239
+ size?: ButtonSize;
216
240
  isDisabled?: boolean;
217
241
  isLoading?: boolean;
218
242
  children?: React.ReactNode;
@@ -242,6 +266,47 @@ const buttonVariantStyles: Record<string, Record<string, string>> = {
242
266
  },
243
267
  };
244
268
 
269
+ const buttonTextColorStyles: Record<string, Record<string, string>> = {
270
+ solid: {
271
+ primary: 'text-primary-foreground',
272
+ secondary: 'text-secondary-foreground',
273
+ positive: 'text-primary-foreground',
274
+ negative: 'text-primary-foreground',
275
+ default: 'text-secondary-foreground',
276
+ },
277
+ outline: {
278
+ primary: 'text-primary',
279
+ secondary: 'text-foreground',
280
+ positive: 'text-emerald-600 dark:text-emerald-400',
281
+ negative: 'text-destructive',
282
+ default: 'text-foreground',
283
+ },
284
+ link: {
285
+ primary: 'text-primary',
286
+ secondary: 'text-foreground',
287
+ positive: 'text-emerald-600 dark:text-emerald-400',
288
+ negative: 'text-destructive',
289
+ default: 'text-primary',
290
+ },
291
+ };
292
+
293
+ function resolveButtonSpinnerColor(
294
+ variant: ButtonVariant,
295
+ action: ButtonAction,
296
+ resolvedTheme: ResolvedTheme
297
+ ): string {
298
+ if (variant === 'solid' && action !== 'secondary' && action !== 'default') {
299
+ return '#FFFFFF';
300
+ }
301
+ if (variant === 'solid') {
302
+ return resolvedTheme === 'dark' ? '#F5EDE0' : '#60321D';
303
+ }
304
+ if (action === 'primary') return '#C0643A';
305
+ if (action === 'positive') return '#059669';
306
+ if (action === 'negative') return resolvedTheme === 'dark' ? '#EF4444' : '#DC2626';
307
+ return resolvedTheme === 'dark' ? '#F7F1E6' : '#0B0B0B';
308
+ }
309
+
245
310
  const buttonSizeStyles: Record<string, string> = {
246
311
  sm: 'px-3 py-1.5 rounded-md min-h-[36px]',
247
312
  md: 'px-4 py-2.5 rounded-lg min-h-[44px]',
@@ -249,9 +314,9 @@ const buttonSizeStyles: Record<string, string> = {
249
314
  };
250
315
 
251
316
  export const Button: React.FC<ButtonProps> = ({
252
- variant = 'solid',
253
- action = 'primary',
254
- size = 'md',
317
+ variant = 'solid' as ButtonVariant,
318
+ action = 'primary' as ButtonAction,
319
+ size = 'md' as ButtonSize,
255
320
  isDisabled = false,
256
321
  isLoading = false,
257
322
  disabled,
@@ -259,20 +324,25 @@ export const Button: React.FC<ButtonProps> = ({
259
324
  children,
260
325
  ...props
261
326
  }) => {
327
+ const { resolvedTheme } = useTimmbrNative();
262
328
  const isActuallyDisabled = isDisabled || isLoading || disabled;
263
329
  const variantClass = buttonVariantStyles[variant]?.[action] || buttonVariantStyles.solid.primary;
264
330
  const sizeClass = buttonSizeStyles[size] || buttonSizeStyles.md;
265
331
  const disabledClass = isActuallyDisabled ? 'opacity-50' : 'active:opacity-85';
332
+ const spinnerColor = resolveButtonSpinnerColor(variant, action, resolvedTheme);
333
+ const styleValue = useMemo(() => ({ variant, action }), [variant, action]);
266
334
 
267
335
  return (
268
- <RNPressable
269
- disabled={isActuallyDisabled}
270
- className={`flex-row items-center justify-center border ${variantClass} ${sizeClass} ${disabledClass} ${className}`}
271
- {...props}
272
- >
273
- {isLoading && <ActivityIndicator color="#FFFFFF" className="mr-2" size="small" />}
274
- {children}
275
- </RNPressable>
336
+ <ButtonStyleContext.Provider value={styleValue}>
337
+ <RNPressable
338
+ disabled={isActuallyDisabled}
339
+ className={`flex-row items-center justify-center border ${variantClass} ${sizeClass} ${disabledClass} ${className}`}
340
+ {...props}
341
+ >
342
+ {isLoading && <ActivityIndicator color={spinnerColor} className="mr-2" size="small" />}
343
+ {children}
344
+ </RNPressable>
345
+ </ButtonStyleContext.Provider>
276
346
  );
277
347
  };
278
348
 
@@ -282,33 +352,64 @@ export interface ButtonTextProps extends TextPropsWithClass {
282
352
  }
283
353
 
284
354
  export const ButtonText: React.FC<ButtonTextProps> = ({
355
+ variant: variantProp,
356
+ action: actionProp,
285
357
  className = '',
286
358
  children,
287
359
  ...props
288
- }) => (
289
- <RNText
290
- className={`text-center font-semibold font-sans text-sm text-primary-foreground ${className}`}
291
- {...props}
292
- >
293
- {children}
294
- </RNText>
295
- );
360
+ }) => {
361
+ const parentStyle = useContext(ButtonStyleContext);
362
+ const variant = variantProp ?? parentStyle.variant;
363
+ const action = actionProp ?? parentStyle.action;
364
+ const defaultColorClass = hasExplicitTextColorClass(className)
365
+ ? ''
366
+ : buttonTextColorStyles[variant]?.[action] || 'text-primary-foreground';
367
+
368
+ return (
369
+ <RNText
370
+ className={`text-center font-semibold font-sans text-sm ${defaultColorClass} ${className}`}
371
+ {...props}
372
+ >
373
+ {children}
374
+ </RNText>
375
+ );
376
+ };
296
377
 
297
378
  export interface ButtonSpinnerProps extends ActivityIndicatorProps {
298
379
  className?: string;
299
380
  }
300
381
 
301
382
  export const ButtonSpinner: React.FC<ButtonSpinnerProps> = ({
302
- color = '#FFFFFF',
383
+ color,
303
384
  size = 'small',
304
385
  className = 'mr-2',
305
386
  ...props
306
- }) => <ActivityIndicator color={color} size={size} className={className} {...props} />;
387
+ }) => {
388
+ const { resolvedTheme } = useTimmbrNative();
389
+ const parentStyle = useContext(ButtonStyleContext);
390
+ const spinnerColor =
391
+ color ?? resolveButtonSpinnerColor(parentStyle.variant, parentStyle.action, resolvedTheme);
392
+
393
+ return <ActivityIndicator color={spinnerColor} size={size} className={className} {...props} />;
394
+ };
395
+
396
+ export type BadgeVariant = 'solid' | 'outline';
397
+ export type BadgeAction = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'muted';
398
+
399
+ interface BadgeStyleContextValue {
400
+ variant: BadgeVariant;
401
+ action: BadgeAction;
402
+ }
403
+
404
+ const BadgeStyleContext = createContext<BadgeStyleContextValue>({
405
+ variant: 'solid',
406
+ action: 'primary',
407
+ });
307
408
 
308
409
  export interface BadgeProps extends ViewProps {
309
410
  className?: string;
310
- variant?: 'solid' | 'outline';
311
- action?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'muted';
411
+ variant?: BadgeVariant;
412
+ action?: BadgeAction;
312
413
  }
313
414
 
314
415
  const badgeActionStyles: Record<string, Record<string, string>> = {
@@ -332,21 +433,46 @@ const badgeActionStyles: Record<string, Record<string, string>> = {
332
433
  },
333
434
  };
334
435
 
436
+ const badgeTextColorStyles: Record<string, Record<string, string>> = {
437
+ solid: {
438
+ primary: 'text-primary-foreground',
439
+ secondary: 'text-secondary-foreground',
440
+ success: 'text-primary-foreground',
441
+ warning: 'text-primary-foreground',
442
+ error: 'text-primary-foreground',
443
+ info: 'text-primary-foreground',
444
+ muted: 'text-foreground',
445
+ },
446
+ outline: {
447
+ primary: 'text-primary',
448
+ secondary: 'text-foreground',
449
+ success: 'text-emerald-600 dark:text-emerald-400',
450
+ warning: 'text-amber-600 dark:text-amber-400',
451
+ error: 'text-destructive',
452
+ info: 'text-blue-600 dark:text-blue-400',
453
+ muted: 'text-muted-foreground',
454
+ },
455
+ };
456
+
335
457
  export const Badge: React.FC<BadgeProps> = ({
336
- variant = 'solid',
337
- action = 'primary',
458
+ variant = 'solid' as BadgeVariant,
459
+ action = 'primary' as BadgeAction,
338
460
  className = '',
339
461
  children,
340
462
  ...props
341
463
  }) => {
342
464
  const badgeClass = badgeActionStyles[variant]?.[action] || badgeActionStyles.solid.primary;
465
+ const styleValue = useMemo(() => ({ variant, action }), [variant, action]);
466
+
343
467
  return (
344
- <View
345
- className={`px-2.5 py-0.5 rounded-full border items-center justify-center flex-row ${badgeClass} ${className}`}
346
- {...props}
347
- >
348
- {children}
349
- </View>
468
+ <BadgeStyleContext.Provider value={styleValue}>
469
+ <View
470
+ className={`px-2.5 py-0.5 rounded-full border items-center justify-center flex-row ${badgeClass} ${className}`}
471
+ {...props}
472
+ >
473
+ {children}
474
+ </View>
475
+ </BadgeStyleContext.Provider>
350
476
  );
351
477
  };
352
478
 
@@ -354,14 +480,21 @@ export const BadgeText: React.FC<TextPropsWithClass> = ({
354
480
  className = '',
355
481
  children,
356
482
  ...props
357
- }) => (
358
- <RNText
359
- className={`text-xs font-semibold font-sans text-primary-foreground ${className}`}
360
- {...props}
361
- >
362
- {children}
363
- </RNText>
364
- );
483
+ }) => {
484
+ const parentStyle = useContext(BadgeStyleContext);
485
+ const defaultColorClass = hasExplicitTextColorClass(className)
486
+ ? ''
487
+ : badgeTextColorStyles[parentStyle.variant]?.[parentStyle.action] || 'text-primary-foreground';
488
+
489
+ return (
490
+ <RNText
491
+ className={`text-xs font-semibold font-sans ${defaultColorClass} ${className}`}
492
+ {...props}
493
+ >
494
+ {children}
495
+ </RNText>
496
+ );
497
+ };
365
498
 
366
499
  export const Card: React.FC<BoxProps> = ({ className = '', children, ...props }) => (
367
500
  <View
@@ -406,15 +539,19 @@ export interface InputFieldProps extends TextInputProps {
406
539
 
407
540
  export const InputField: React.FC<InputFieldProps> = ({
408
541
  className = '',
409
- placeholderTextColor = '#6D6D6D',
542
+ placeholderTextColor,
410
543
  ...props
411
- }) => (
412
- <TextInput
413
- className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base py-1 ${className}`}
414
- placeholderTextColor={placeholderTextColor}
415
- {...props}
416
- />
417
- );
544
+ }) => {
545
+ const { resolvedTheme } = useTimmbrNative();
546
+ const defaultPlaceholderColor = resolvedTheme === 'dark' ? '#9D9D9D' : '#6D6D6D';
547
+ return (
548
+ <TextInput
549
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base py-1 ${className}`}
550
+ placeholderTextColor={placeholderTextColor ?? defaultPlaceholderColor}
551
+ {...props}
552
+ />
553
+ );
554
+ };
418
555
 
419
556
  export const Textarea: React.FC<InputProps> = ({
420
557
  isInvalid = false,
@@ -440,17 +577,21 @@ export const Textarea: React.FC<InputProps> = ({
440
577
 
441
578
  export const TextareaInput: React.FC<InputFieldProps> = ({
442
579
  className = '',
443
- placeholderTextColor = '#6D6D6D',
580
+ placeholderTextColor,
444
581
  ...props
445
- }) => (
446
- <TextInput
447
- multiline
448
- textAlignVertical="top"
449
- className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base ${className}`}
450
- placeholderTextColor={placeholderTextColor}
451
- {...props}
452
- />
453
- );
582
+ }) => {
583
+ const { resolvedTheme } = useTimmbrNative();
584
+ const defaultPlaceholderColor = resolvedTheme === 'dark' ? '#9D9D9D' : '#6D6D6D';
585
+ return (
586
+ <TextInput
587
+ multiline
588
+ textAlignVertical="top"
589
+ className={`flex-1 text-foreground placeholder:text-muted-foreground font-sans text-base ${className}`}
590
+ placeholderTextColor={placeholderTextColor ?? defaultPlaceholderColor}
591
+ {...props}
592
+ />
593
+ );
594
+ };
454
595
 
455
596
  interface TabsContextValue {
456
597
  activeTab: string;
@@ -570,18 +711,21 @@ export interface ActionsheetProps {
570
711
  children: React.ReactNode;
571
712
  }
572
713
 
573
- export const Actionsheet: React.FC<ActionsheetProps> = ({ isOpen, onClose, children }) => (
574
- <RNModal
575
- visible={isOpen}
576
- transparent
577
- animationType="fade"
578
- onRequestClose={onClose}
579
- >
580
- <View className="flex-1 justify-end">
581
- {children}
582
- </View>
583
- </RNModal>
584
- );
714
+ export const Actionsheet: React.FC<ActionsheetProps> = ({ isOpen, onClose, children }) => {
715
+ const { resolvedTheme } = useTimmbrNative();
716
+ return (
717
+ <RNModal
718
+ visible={isOpen}
719
+ transparent
720
+ animationType="fade"
721
+ onRequestClose={onClose}
722
+ >
723
+ <View className={`flex-1 justify-end ${resolvedTheme === 'dark' ? 'dark' : ''}`}>
724
+ {children}
725
+ </View>
726
+ </RNModal>
727
+ );
728
+ };
585
729
 
586
730
  export const ActionsheetBackdrop: React.FC<PressableProps & { className?: string }> = ({
587
731
  onPress,
@@ -652,39 +796,54 @@ export const Modal: React.FC<CustomModalProps> = ({
652
796
  isOpen = false,
653
797
  visible,
654
798
  onClose,
799
+ className = '',
655
800
  children,
656
801
  ...props
657
- }) => (
658
- <RNModal
659
- visible={isOpen || visible}
660
- transparent
661
- animationType="fade"
662
- onRequestClose={onClose}
663
- {...props}
664
- >
665
- <View className="flex-1 items-center justify-center bg-black/60 p-4">
666
- {children}
667
- </View>
668
- </RNModal>
669
- );
802
+ }) => {
803
+ const { resolvedTheme } = useTimmbrNative();
804
+ return (
805
+ <RNModal
806
+ visible={isOpen || visible}
807
+ transparent
808
+ animationType="fade"
809
+ onRequestClose={onClose}
810
+ {...props}
811
+ >
812
+ <View
813
+ className={`flex-1 items-center justify-center bg-black/60 p-4 ${
814
+ resolvedTheme === 'dark' ? 'dark' : ''
815
+ } ${className}`}
816
+ >
817
+ {children}
818
+ </View>
819
+ </RNModal>
820
+ );
821
+ };
670
822
 
671
823
  export interface CustomSwitchProps extends SwitchProps {
672
824
  className?: string;
673
825
  }
674
826
 
675
827
  export const Switch: React.FC<CustomSwitchProps> = ({
676
- trackColor = { false: '#DDD5CA', true: '#C0643A' },
828
+ trackColor,
677
829
  thumbColor = '#FFFFFF',
678
830
  className = '',
679
831
  ...props
680
- }) => (
681
- <RNSwitch
682
- trackColor={trackColor}
683
- thumbColor={thumbColor}
684
- className={className}
685
- {...props}
686
- />
687
- );
832
+ }) => {
833
+ const { resolvedTheme } = useTimmbrNative();
834
+ const defaultTrackColor = {
835
+ false: resolvedTheme === 'dark' ? '#3C3C3C' : '#DDD5CA',
836
+ true: '#C0643A',
837
+ };
838
+ return (
839
+ <RNSwitch
840
+ trackColor={trackColor ?? defaultTrackColor}
841
+ thumbColor={thumbColor}
842
+ className={className}
843
+ {...props}
844
+ />
845
+ );
846
+ };
688
847
 
689
848
  export const Spinner: React.FC<ButtonSpinnerProps> = ({
690
849
  color = '#C0643A',
@@ -780,20 +939,81 @@ export const Toast: React.FC<ToastProps> = ({
780
939
  };
781
940
 
782
941
  export interface IconProps {
783
- as?: React.ComponentType<{ size?: number; color?: string; className?: string }>;
942
+ as?: React.ElementType;
784
943
  size?: number | string;
785
944
  color?: string;
786
945
  className?: string;
787
946
  }
788
947
 
948
+ function getSemanticIconColorMap(resolvedTheme: ResolvedTheme): Record<string, string> {
949
+ const isDark = resolvedTheme === 'dark';
950
+ return {
951
+ 'text-foreground': isDark ? '#F7F1E6' : '#0B0B0B',
952
+ 'text-card-foreground': isDark ? '#F7F1E6' : '#0B0B0B',
953
+ 'text-muted-foreground': isDark ? '#9D9D9D' : '#6D6D6D',
954
+ 'text-secondary-foreground': isDark ? '#F5EDE0' : '#60321D',
955
+ 'text-accent-foreground': isDark ? '#F5EDE0' : '#60321D',
956
+ 'text-primary': '#C0643A',
957
+ 'text-primary-foreground': '#FFFFFF',
958
+ 'text-white': '#FFFFFF',
959
+ 'text-destructive': isDark ? '#EF4444' : '#DC2626',
960
+ 'text-destructive-foreground': isDark ? '#F7F1E6' : '#FFFFFF',
961
+ 'text-success': '#059669',
962
+ 'text-emerald-500': '#10B981',
963
+ 'text-emerald-600': '#059669',
964
+ 'text-warning': '#D97706',
965
+ 'text-amber-400': '#FBBF24',
966
+ 'text-amber-500': '#F59E0B',
967
+ 'text-amber-600': '#D97706',
968
+ 'text-info': '#2563EB',
969
+ 'text-blue-500': '#3B82F6',
970
+ 'text-blue-600': '#2563EB',
971
+ };
972
+ }
973
+
974
+ function resolveIconColor(
975
+ explicitColor: string | undefined,
976
+ className: string,
977
+ resolvedTheme: ResolvedTheme
978
+ ): string {
979
+ if (explicitColor && explicitColor !== 'currentColor') {
980
+ return explicitColor;
981
+ }
982
+
983
+ const tokens = className.split(/\s+/).filter(Boolean);
984
+ const colorMap = getSemanticIconColorMap(resolvedTheme);
985
+
986
+ if (resolvedTheme === 'dark') {
987
+ for (const token of tokens) {
988
+ if (token.startsWith('dark:')) {
989
+ const unprefixed = token.slice('dark:'.length);
990
+ const hexMatch = /^text-\[(#[0-9a-fA-F]{3,8})\]$/.exec(unprefixed);
991
+ if (hexMatch?.[1]) return hexMatch[1];
992
+ if (colorMap[unprefixed]) return colorMap[unprefixed];
993
+ }
994
+ }
995
+ }
996
+
997
+ for (const token of tokens) {
998
+ if (token.startsWith('dark:')) continue;
999
+ const hexMatch = /^text-\[(#[0-9a-fA-F]{3,8})\]$/.exec(token);
1000
+ if (hexMatch?.[1]) return hexMatch[1];
1001
+ if (colorMap[token]) return colorMap[token];
1002
+ }
1003
+
1004
+ return resolvedTheme === 'dark' ? '#F7F1E6' : '#0B0B0B';
1005
+ }
1006
+
789
1007
  export const Icon: React.FC<IconProps> = ({
790
1008
  as: Component,
791
1009
  size = 20,
792
- color = 'currentColor',
1010
+ color,
793
1011
  className = '',
794
1012
  }) => {
1013
+ const { resolvedTheme } = useTimmbrNative();
795
1014
  if (!Component) return null;
796
1015
  const numSize = typeof size === 'number' ? size : parseInt(size, 10) || 20;
797
- return <Component size={numSize} color={color} className={className} />;
1016
+ const resolvedColor = resolveIconColor(color, className, resolvedTheme);
1017
+ return <Component size={numSize} color={resolvedColor} className={className} />;
798
1018
  };
799
1019