@umituz/react-native-design-system 1.15.0 → 2.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 (88) hide show
  1. package/package.json +26 -19
  2. package/src/atoms/AtomicAvatar.tsx +161 -0
  3. package/src/atoms/AtomicButton.tsx +241 -0
  4. package/src/atoms/AtomicCard.tsx +84 -0
  5. package/src/atoms/AtomicChip.tsx +226 -0
  6. package/src/atoms/AtomicDatePicker.tsx +255 -0
  7. package/src/atoms/AtomicFab.tsx +99 -0
  8. package/src/atoms/AtomicIcon.tsx +149 -0
  9. package/src/atoms/AtomicInput.tsx +308 -0
  10. package/src/atoms/AtomicPicker.tsx +310 -0
  11. package/src/atoms/AtomicProgress.tsx +149 -0
  12. package/src/atoms/AtomicText.tsx +55 -0
  13. package/src/atoms/__tests__/AtomicButton.test.tsx +107 -0
  14. package/src/atoms/__tests__/AtomicIcon.test.tsx +110 -0
  15. package/src/atoms/__tests__/AtomicInput.test.tsx +195 -0
  16. package/src/atoms/datepicker/components/DatePickerButton.tsx +112 -0
  17. package/src/atoms/datepicker/components/DatePickerModal.tsx +143 -0
  18. package/src/atoms/fab/styles/fabStyles.ts +98 -0
  19. package/src/atoms/fab/types/index.ts +88 -0
  20. package/src/atoms/index.ts +70 -0
  21. package/src/atoms/input/hooks/useInputState.ts +63 -0
  22. package/src/atoms/input/styles/inputStylesHelper.ts +120 -0
  23. package/src/atoms/picker/components/PickerChips.tsx +57 -0
  24. package/src/atoms/picker/components/PickerModal.tsx +214 -0
  25. package/src/atoms/picker/styles/pickerStyles.ts +223 -0
  26. package/src/atoms/picker/types/index.ts +42 -0
  27. package/src/index.ts +133 -52
  28. package/src/molecules/ConfirmationModal.tsx +42 -0
  29. package/src/molecules/ConfirmationModalContent.tsx +87 -0
  30. package/src/molecules/ConfirmationModalMain.tsx +91 -0
  31. package/src/molecules/FormField.tsx +155 -0
  32. package/src/molecules/IconContainer.tsx +79 -0
  33. package/src/molecules/ListItem.tsx +35 -0
  34. package/src/molecules/ScreenHeader.tsx +171 -0
  35. package/src/molecules/SearchBar.tsx +198 -0
  36. package/src/molecules/confirmation-modal/components.tsx +94 -0
  37. package/src/molecules/confirmation-modal/index.ts +7 -0
  38. package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
  39. package/src/molecules/confirmation-modal/types/index.ts +41 -0
  40. package/src/molecules/confirmation-modal/useConfirmationModal.ts +50 -0
  41. package/src/molecules/index.ts +19 -0
  42. package/src/molecules/listitem/index.ts +6 -0
  43. package/src/molecules/listitem/styles/listItemStyles.ts +37 -0
  44. package/src/molecules/listitem/types/index.ts +21 -0
  45. package/src/organisms/AppHeader.tsx +136 -0
  46. package/src/organisms/FormContainer.tsx +169 -0
  47. package/src/organisms/ScreenLayout.tsx +183 -0
  48. package/src/organisms/index.ts +31 -0
  49. package/src/responsive/config.ts +139 -0
  50. package/src/responsive/deviceDetection.ts +155 -0
  51. package/src/responsive/gridUtils.ts +79 -0
  52. package/src/responsive/index.ts +52 -0
  53. package/src/responsive/platformConstants.ts +98 -0
  54. package/src/responsive/responsive.ts +61 -0
  55. package/src/responsive/responsiveLayout.ts +137 -0
  56. package/src/responsive/responsiveSizing.ts +134 -0
  57. package/src/responsive/useResponsive.ts +140 -0
  58. package/src/responsive/validation.ts +158 -0
  59. package/src/theme/core/BaseTokens.ts +42 -0
  60. package/src/theme/core/ColorPalette.ts +29 -0
  61. package/src/theme/core/CustomColors.ts +122 -0
  62. package/src/theme/core/NavigationTheme.ts +72 -0
  63. package/src/theme/core/TokenFactory.ts +103 -0
  64. package/src/theme/core/colors/ColorUtils.ts +53 -0
  65. package/src/theme/core/colors/DarkColors.ts +146 -0
  66. package/src/theme/core/colors/LightColors.ts +146 -0
  67. package/src/theme/core/constants/DesignConstants.ts +31 -0
  68. package/src/theme/core/themes.ts +118 -0
  69. package/src/theme/core/tokens/BaseTokens.ts +144 -0
  70. package/src/theme/core/tokens/Borders.ts +43 -0
  71. package/src/theme/core/tokens/Sizes.ts +51 -0
  72. package/src/theme/core/tokens/Spacing.ts +38 -0
  73. package/src/theme/core/tokens/Typography.ts +143 -0
  74. package/src/theme/hooks/useAppDesignTokens.ts +45 -0
  75. package/src/theme/hooks/useCommonStyles.ts +248 -0
  76. package/src/theme/hooks/useThemedStyles.ts +68 -0
  77. package/src/theme/index.ts +94 -0
  78. package/src/theme/infrastructure/globalThemeStore.ts +69 -0
  79. package/src/theme/infrastructure/storage/ThemeStorage.ts +93 -0
  80. package/src/theme/infrastructure/stores/themeStore.ts +109 -0
  81. package/src/typography/__tests__/colorValidationUtils.test.ts +180 -0
  82. package/src/typography/__tests__/textColorUtils.test.ts +185 -0
  83. package/src/typography/__tests__/textStyleUtils.test.ts +168 -0
  84. package/src/typography/domain/entities/TypographyTypes.ts +88 -0
  85. package/src/typography/index.ts +53 -0
  86. package/src/typography/presentation/utils/colorValidationUtils.ts +133 -0
  87. package/src/typography/presentation/utils/textColorUtils.ts +205 -0
  88. package/src/typography/presentation/utils/textStyleUtils.ts +159 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Tests for typography style utilities
3
+ */
4
+
5
+ import { getTextStyle, isTextStyleVariant, getAllTextStyleVariants } from '../presentation/utils/textStyleUtils';
6
+ import type { TextStyleVariant } from '../domain/entities/TypographyTypes';
7
+ import type { DesignTokens } from '@umituz/react-native-design-system-theme';
8
+
9
+ const mockTokens: DesignTokens = {
10
+ colors: {
11
+ textPrimary: '#000000',
12
+ textSecondary: '#666666',
13
+ textTertiary: '#999999',
14
+ textDisabled: '#CCCCCC',
15
+ textInverse: '#FFFFFF',
16
+ onSurface: '#000000',
17
+ onBackground: '#000000',
18
+ onPrimary: '#FFFFFF',
19
+ onSecondary: '#FFFFFF',
20
+ onSuccess: '#FFFFFF',
21
+ onError: '#FFFFFF',
22
+ onWarning: '#000000',
23
+ onInfo: '#FFFFFF',
24
+ success: '#4CAF50',
25
+ error: '#F44336',
26
+ warning: '#FF9800',
27
+ info: '#2196F3',
28
+ primary: '#2196F3',
29
+ secondary: '#FF9800',
30
+ tertiary: '#9C27B0',
31
+ surface: '#FFFFFF',
32
+ surfaceVariant: '#F5F5F5',
33
+ background: '#FFFFFF',
34
+ },
35
+ typography: {
36
+ displayLarge: { fontSize: 57, fontWeight: '400' },
37
+ displayMedium: { fontSize: 45, fontWeight: '400' },
38
+ displaySmall: { fontSize: 36, fontWeight: '400' },
39
+ headlineLarge: { fontSize: 32, fontWeight: '400' },
40
+ headlineMedium: { fontSize: 28, fontWeight: '400' },
41
+ headlineSmall: { fontSize: 24, fontWeight: '400' },
42
+ titleLarge: { fontSize: 22, fontWeight: '500' },
43
+ titleMedium: { fontSize: 16, fontWeight: '500' },
44
+ titleSmall: { fontSize: 14, fontWeight: '500' },
45
+ bodyLarge: { fontSize: 16, fontWeight: '400' },
46
+ bodyMedium: { fontSize: 14, fontWeight: '400' },
47
+ bodySmall: { fontSize: 12, fontWeight: '400' },
48
+ labelLarge: { fontSize: 14, fontWeight: '500' },
49
+ labelMedium: { fontSize: 12, fontWeight: '500' },
50
+ labelSmall: { fontSize: 11, fontWeight: '500' },
51
+ },
52
+ spacing: {},
53
+ shadows: {},
54
+ borderRadius: {},
55
+ iconSizes: {},
56
+ opacity: {},
57
+ avatarSizes: {},
58
+ borders: {},
59
+ };
60
+
61
+ describe('getTextStyle', () => {
62
+ test('should throw error when tokens is null', () => {
63
+ expect(() => getTextStyle('bodyLarge', null as any)).toThrow(
64
+ 'Invalid design tokens: tokens and tokens.typography are required'
65
+ );
66
+ });
67
+
68
+ test('should throw error when tokens is undefined', () => {
69
+ expect(() => getTextStyle('bodyLarge', undefined as any)).toThrow(
70
+ 'Invalid design tokens: tokens and tokens.typography are required'
71
+ );
72
+ });
73
+
74
+ test('should throw error when tokens.typography is null', () => {
75
+ expect(() => getTextStyle('bodyLarge', { typography: null } as any)).toThrow(
76
+ 'Invalid design tokens: tokens and tokens.typography are required'
77
+ );
78
+ });
79
+
80
+ test('should return correct style for displayLarge', () => {
81
+ const result = getTextStyle('displayLarge', mockTokens);
82
+ expect(result).toEqual({ fontSize: 57, fontWeight: '400' });
83
+ });
84
+
85
+ test('should return correct style for headlineMedium', () => {
86
+ const result = getTextStyle('headlineMedium', mockTokens);
87
+ expect(result).toEqual({ fontSize: 28, fontWeight: '400' });
88
+ });
89
+
90
+ test('should return correct style for titleSmall', () => {
91
+ const result = getTextStyle('titleSmall', mockTokens);
92
+ expect(result).toEqual({ fontSize: 14, fontWeight: '500' });
93
+ });
94
+
95
+ test('should return correct style for bodyMedium', () => {
96
+ const result = getTextStyle('bodyMedium', mockTokens);
97
+ expect(result).toEqual({ fontSize: 14, fontWeight: '400' });
98
+ });
99
+
100
+ test('should return correct style for labelSmall', () => {
101
+ const result = getTextStyle('labelSmall', mockTokens);
102
+ expect(result).toEqual({ fontSize: 11, fontWeight: '500' });
103
+ });
104
+
105
+ test('should return fallback style for unknown variant', () => {
106
+ const result = getTextStyle('unknownVariant' as TextStyleVariant, mockTokens);
107
+ expect(result).toEqual({ fontSize: 16, fontWeight: '400' }); // bodyLarge fallback
108
+ });
109
+ });
110
+
111
+ describe('isTextStyleVariant', () => {
112
+ test('should return true for valid variants', () => {
113
+ expect(isTextStyleVariant('displayLarge')).toBe(true);
114
+ expect(isTextStyleVariant('headlineMedium')).toBe(true);
115
+ expect(isTextStyleVariant('titleSmall')).toBe(true);
116
+ expect(isTextStyleVariant('bodyMedium')).toBe(true);
117
+ expect(isTextStyleVariant('labelSmall')).toBe(true);
118
+ });
119
+
120
+ test('should return false for invalid variants', () => {
121
+ expect(isTextStyleVariant('invalid')).toBe(false);
122
+ expect(isTextStyleVariant('display')).toBe(false);
123
+ expect(isTextStyleVariant('')).toBe(false);
124
+ expect(isTextStyleVariant('body')).toBe(false);
125
+ });
126
+ });
127
+
128
+ describe('getAllTextStyleVariants', () => {
129
+ test('should return all text style variants', () => {
130
+ const variants = getAllTextStyleVariants();
131
+
132
+ expect(variants).toHaveLength(15);
133
+ expect(variants).toContain('displayLarge');
134
+ expect(variants).toContain('displayMedium');
135
+ expect(variants).toContain('displaySmall');
136
+ expect(variants).toContain('headlineLarge');
137
+ expect(variants).toContain('headlineMedium');
138
+ expect(variants).toContain('headlineSmall');
139
+ expect(variants).toContain('titleLarge');
140
+ expect(variants).toContain('titleMedium');
141
+ expect(variants).toContain('titleSmall');
142
+ expect(variants).toContain('bodyLarge');
143
+ expect(variants).toContain('bodyMedium');
144
+ expect(variants).toContain('bodySmall');
145
+ expect(variants).toContain('labelLarge');
146
+ expect(variants).toContain('labelMedium');
147
+ expect(variants).toContain('labelSmall');
148
+ });
149
+
150
+ test('should return readonly array', () => {
151
+ const variants = getAllTextStyleVariants();
152
+ expect(Array.isArray(variants)).toBe(true);
153
+ });
154
+ });
155
+
156
+ describe('TextStyleVariant validation', () => {
157
+ test('should validate all defined text style variants', () => {
158
+ const validVariants: TextStyleVariant[] = getAllTextStyleVariants();
159
+
160
+ validVariants.forEach(variant => {
161
+ const result = getTextStyle(variant, mockTokens);
162
+ expect(result).toHaveProperty('fontSize');
163
+ expect(result).toHaveProperty('fontWeight');
164
+ expect(typeof result.fontSize).toBe('number');
165
+ expect(typeof result.fontWeight).toBe('string');
166
+ });
167
+ });
168
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Typography Types - Material Design 3 Text Style Variants
3
+ *
4
+ * This file defines all typography-related types for React Native applications.
5
+ * These types are used by components like AtomicText to ensure type-safe typography usage.
6
+ *
7
+ * @fileoverview Provides type definitions for Material Design 3 typography system
8
+ * @author Ümit UZ <umit@umituz.com>
9
+ * @since 1.0.0
10
+ * @version 1.2.0
11
+ */
12
+
13
+ /**
14
+ * Material Design 3 Text Style Variants
15
+ *
16
+ * These variants correspond to Material Design 3 typography scale:
17
+ * - Display: Largest text (57px, 45px, 36px)
18
+ * - Headline: Section headers (32px, 28px, 24px)
19
+ * - Title: Card titles, list headers (22px, 16px, 14px)
20
+ * - Body: Main content text (16px, 14px, 12px)
21
+ * - Label: UI labels, buttons (14px, 12px, 11px)
22
+ */
23
+ export type TextStyleVariant =
24
+ | 'displayLarge'
25
+ | 'displayMedium'
26
+ | 'displaySmall'
27
+ | 'headlineLarge'
28
+ | 'headlineMedium'
29
+ | 'headlineSmall'
30
+ | 'titleLarge'
31
+ | 'titleMedium'
32
+ | 'titleSmall'
33
+ | 'bodyLarge'
34
+ | 'bodyMedium'
35
+ | 'bodySmall'
36
+ | 'labelLarge'
37
+ | 'labelMedium'
38
+ | 'labelSmall';
39
+
40
+ /**
41
+ * Material Design 3 Text Color Variants
42
+ *
43
+ * TEXT COLORS (for text on surfaces):
44
+ * - textPrimary, textSecondary, textTertiary: General text colors
45
+ * - onSurface, onBackground: Text on surface/background
46
+ *
47
+ * ON COLORS (for text on colored backgrounds):
48
+ * - onPrimary, onSecondary: Text on primary/secondary colored backgrounds
49
+ * - onSuccess, onError, onWarning, onInfo: Text on semantic colored backgrounds
50
+ *
51
+ * SEMANTIC COLORS (can be used as text colors):
52
+ * - success, error, warning, info: Semantic colors (can be text or background)
53
+ *
54
+ * NOTE: 'primary' and 'secondary' are BACKGROUND colors, not text colors.
55
+ * Use 'onPrimary'/'onSecondary' for text on colored backgrounds, or
56
+ * 'textPrimary'/'textSecondary' for general text.
57
+ */
58
+ export type ColorVariant =
59
+ // General text colors (Material Design 3)
60
+ | 'textPrimary'
61
+ | 'textSecondary'
62
+ | 'textTertiary'
63
+ | 'textDisabled'
64
+ | 'textInverse'
65
+ // Text on surfaces (Material Design 3)
66
+ | 'onSurface'
67
+ | 'onBackground'
68
+ // Text on colored backgrounds (Material Design 3)
69
+ | 'onPrimary'
70
+ | 'onSecondary'
71
+ | 'onSuccess'
72
+ | 'onError'
73
+ | 'onWarning'
74
+ | 'onInfo'
75
+ // Semantic colors (can be used as text)
76
+ | 'success'
77
+ | 'error'
78
+ | 'warning'
79
+ | 'info'
80
+ // Legacy support (deprecated - use textPrimary/textSecondary instead)
81
+ | 'primary'
82
+ | 'secondary'
83
+ | 'tertiary'
84
+ | 'disabled'
85
+ | 'inverse'
86
+ // Legacy: surfaceVariant is a background color, maps to textSecondary
87
+ | 'surfaceVariant';
88
+
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @umituz/react-native-design-system-typography - Public API
3
+ *
4
+ * Typography types and utilities for React Native applications
5
+ * Material Design 3 text styles and color variants
6
+ *
7
+ * This package provides:
8
+ * - Typography type definitions (TextStyleVariant, ColorVariant)
9
+ * - Text color utility functions
10
+ * - Type-safe typography helpers
11
+ *
12
+ * Usage:
13
+ * import { TextStyleVariant, ColorVariant, getTextColor } from '@umituz/react-native-design-system-typography';
14
+ */
15
+
16
+ // =============================================================================
17
+ // DOMAIN LAYER - Entities (Types)
18
+ // =============================================================================
19
+
20
+ export type {
21
+ TextStyleVariant,
22
+ ColorVariant,
23
+ } from './domain/entities/TypographyTypes';
24
+
25
+ // =============================================================================
26
+ // PRESENTATION LAYER - Utilities
27
+ // =============================================================================
28
+
29
+ export {
30
+ getTextColor,
31
+ } from './presentation/utils/textColorUtils';
32
+
33
+ export {
34
+ getTextStyle,
35
+ isTextStyleVariant,
36
+ getAllTextStyleVariants,
37
+ clearTypographyCache,
38
+ } from './presentation/utils/textStyleUtils';
39
+
40
+ export {
41
+ clearColorCache,
42
+ } from './presentation/utils/textColorUtils';
43
+
44
+ export {
45
+ isValidHexColor,
46
+ isValidRgbColor,
47
+ isValidHslColor,
48
+ isValidNamedColor,
49
+ isValidColor,
50
+ getColorFormat,
51
+ normalizeColor,
52
+ } from './presentation/utils/colorValidationUtils';
53
+
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Color Validation Utilities
3
+ *
4
+ * Helper functions for validating color formats and values.
5
+ * These utilities ensure color strings are in valid formats.
6
+ *
7
+ * @fileoverview Provides color validation utilities
8
+ * @author Ümit UZ <umit@umituz.com>
9
+ * @since 1.2.0
10
+ * @version 1.2.0
11
+ */
12
+
13
+ /**
14
+ * Regular expressions for color format validation
15
+ */
16
+ const COLOR_PATTERNS = {
17
+ // #RGB, #RRGGBB
18
+ hex: /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/,
19
+ // rgb(R, G, B), rgba(R, G, B, A)
20
+ rgb: /^rgba?\(\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*,\s*(\d{1,3}%?)\s*(?:,\s*([01]?\.?\d*)\s*)?\)$/,
21
+ // hsl(H, S, L), hsla(H, S, L, A)
22
+ hsl: /^hsla?\(\s*(\d{1,3})\s*,\s*(\d{1,3}%)\s*,\s*(\d{1,3}%)\s*(?:,\s*([01]?\.?\d*)\s*)?\)$/,
23
+ // CSS color names (limited to common ones)
24
+ named: /^(red|blue|green|yellow|orange|purple|pink|brown|black|white|gray|grey|transparent|inherit|initial|unset|currentcolor)$/i,
25
+ };
26
+
27
+ /**
28
+ * Check if a string is a valid hex color
29
+ *
30
+ * @param color - Color string to validate
31
+ * @returns True if valid hex color
32
+ */
33
+ export function isValidHexColor(color: string): boolean {
34
+ return COLOR_PATTERNS.hex.test(color);
35
+ }
36
+
37
+ /**
38
+ * Check if a string is a valid RGB/RGBA color
39
+ *
40
+ * @param color - Color string to validate
41
+ * @returns True if valid RGB/RGBA color
42
+ */
43
+ export function isValidRgbColor(color: string): boolean {
44
+ if (!COLOR_PATTERNS.rgb.test(color)) {
45
+ return false;
46
+ }
47
+
48
+ // Additional validation for RGB values
49
+ const match = color.match(/\d+/g);
50
+ if (!match || match.length < 3) return false;
51
+
52
+ const [r, g, b] = match.map(Number);
53
+ return r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255;
54
+ }
55
+
56
+ /**
57
+ * Check if a string is a valid HSL/HSLA color
58
+ *
59
+ * @param color - Color string to validate
60
+ * @returns True if valid HSL/HSLA color
61
+ */
62
+ export function isValidHslColor(color: string): boolean {
63
+ if (!COLOR_PATTERNS.hsl.test(color)) {
64
+ return false;
65
+ }
66
+
67
+ // Additional validation for HSL values
68
+ const match = color.match(/\d+/g);
69
+ if (!match || match.length < 3) return false;
70
+
71
+ const [h, s, l] = match.map(Number);
72
+ return h >= 0 && h <= 360 && s >= 0 && s <= 100 && l >= 0 && l <= 100;
73
+ }
74
+
75
+ /**
76
+ * Check if a string is a valid CSS named color
77
+ *
78
+ * @param color - Color string to validate
79
+ * @returns True if valid CSS named color
80
+ */
81
+ export function isValidNamedColor(color: string): boolean {
82
+ return COLOR_PATTERNS.named.test(color);
83
+ }
84
+
85
+ /**
86
+ * Check if a string is a valid color in any supported format
87
+ *
88
+ * @param color - Color string to validate
89
+ * @returns True if valid color in any format
90
+ */
91
+ export function isValidColor(color: string): boolean {
92
+ return isValidHexColor(color) ||
93
+ isValidRgbColor(color) ||
94
+ isValidHslColor(color) ||
95
+ isValidNamedColor(color);
96
+ }
97
+
98
+ /**
99
+ * Get the format of a color string
100
+ *
101
+ * @param color - Color string to analyze
102
+ * @returns Color format or 'unknown' if not recognized
103
+ */
104
+ export function getColorFormat(color: string): 'hex' | 'rgb' | 'hsl' | 'named' | 'unknown' {
105
+ if (isValidHexColor(color)) return 'hex';
106
+ if (isValidRgbColor(color)) return 'rgb';
107
+ if (isValidHslColor(color)) return 'hsl';
108
+ if (isValidNamedColor(color)) return 'named';
109
+ return 'unknown';
110
+ }
111
+
112
+ /**
113
+ * Normalize a color string to a consistent format
114
+ *
115
+ * @param color - Color string to normalize
116
+ * @returns Normalized color string
117
+ */
118
+ export function normalizeColor(color: string): string {
119
+ if (!isValidColor(color)) {
120
+ return color;
121
+ }
122
+
123
+ // Convert 3-digit hex to 6-digit
124
+ if (isValidHexColor(color) && color.length === 4) {
125
+ const r = color[1];
126
+ const g = color[2];
127
+ const b = color[3];
128
+ return `#${r}${r}${g}${g}${b}${b}`;
129
+ }
130
+
131
+ // Convert to lowercase for consistency
132
+ return color.toLowerCase();
133
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Text Color Utilities
3
+ *
4
+ * Helper functions for resolving text colors from design tokens.
5
+ * These utilities are used by components like AtomicText to map color variants
6
+ * to actual color values from theme.
7
+ *
8
+ * @fileoverview Provides color resolution utilities for Material Design 3
9
+ * @author Ümit UZ <umit@umituz.com>
10
+ * @since 1.0.0
11
+ * @version 1.2.0
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { getTextColor } from '@umituz/react-native-design-system-typography';
16
+ * import { useAppDesignTokens } from '@umituz/react-native-design-system-theme';
17
+ *
18
+ * const MyComponent = () => {
19
+ * const tokens = useAppDesignTokens();
20
+ * const textColor = getTextColor('textPrimary', tokens);
21
+ * return <Text style={{ color: textColor }}>Hello</Text>;
22
+ * };
23
+ * ```
24
+ */
25
+
26
+ import type { ColorVariant } from '../../domain/entities/TypographyTypes';
27
+ import type { DesignTokens } from '../../../theme';
28
+
29
+ // Cache for color variant validation to improve performance
30
+ const COLOR_VARIANT_SET = new Set<string>([
31
+ 'textPrimary',
32
+ 'textSecondary',
33
+ 'textTertiary',
34
+ 'textDisabled',
35
+ 'textInverse',
36
+ 'onSurface',
37
+ 'onBackground',
38
+ 'onPrimary',
39
+ 'onSecondary',
40
+ 'onSuccess',
41
+ 'onError',
42
+ 'onWarning',
43
+ 'onInfo',
44
+ 'success',
45
+ 'error',
46
+ 'warning',
47
+ 'info',
48
+ 'primary',
49
+ 'secondary',
50
+ 'tertiary',
51
+ 'disabled',
52
+ 'inverse',
53
+ 'surfaceVariant',
54
+ ]);
55
+
56
+ /**
57
+ * Color mapper interface for dependency injection
58
+ */
59
+ interface ColorMapper {
60
+ getColor(color: ColorVariant, tokens: DesignTokens): string;
61
+ }
62
+
63
+ /**
64
+ * Material Design 3 color mapper implementation
65
+ */
66
+ class MaterialColorMapper implements ColorMapper {
67
+ getColor(color: ColorVariant, tokens: DesignTokens): string {
68
+ switch (color) {
69
+ // General text colors (Material Design 3)
70
+ case 'textPrimary':
71
+ return tokens.colors.textPrimary;
72
+ case 'textSecondary':
73
+ return tokens.colors.textSecondary;
74
+ case 'textTertiary':
75
+ return tokens.colors.textTertiary;
76
+ case 'textDisabled':
77
+ return tokens.colors.textDisabled;
78
+ case 'textInverse':
79
+ return tokens.colors.textInverse;
80
+
81
+ // Text on surfaces (Material Design 3)
82
+ case 'onSurface':
83
+ return tokens.colors.onSurface;
84
+ case 'onBackground':
85
+ return tokens.colors.onBackground;
86
+
87
+ // Text on colored backgrounds (Material Design 3)
88
+ case 'onPrimary':
89
+ return tokens.colors.onPrimary;
90
+ case 'onSecondary':
91
+ return tokens.colors.onSecondary;
92
+ case 'onSuccess':
93
+ return tokens.colors.onSuccess;
94
+ case 'onError':
95
+ return tokens.colors.onError;
96
+ case 'onWarning':
97
+ return tokens.colors.onWarning;
98
+ case 'onInfo':
99
+ return tokens.colors.onInfo;
100
+
101
+ // Semantic colors (can be used as text)
102
+ case 'success':
103
+ return tokens.colors.success;
104
+ case 'error':
105
+ return tokens.colors.error;
106
+ case 'warning':
107
+ return tokens.colors.warning;
108
+ case 'info':
109
+ return tokens.colors.info;
110
+
111
+ // Legacy support (deprecated - maps to new names)
112
+ case 'primary':
113
+ return tokens.colors.textPrimary;
114
+ case 'secondary':
115
+ return tokens.colors.textSecondary;
116
+ case 'tertiary':
117
+ return tokens.colors.textTertiary;
118
+ case 'disabled':
119
+ return tokens.colors.textDisabled;
120
+ case 'inverse':
121
+ return tokens.colors.textInverse;
122
+ case 'surfaceVariant':
123
+ return tokens.colors.textSecondary;
124
+
125
+ default:
126
+ if (__DEV__) {
127
+ // eslint-disable-next-line no-console
128
+ console.warn(`Unknown color variant: ${color}`);
129
+ }
130
+ return tokens.colors.textPrimary;
131
+ }
132
+ }
133
+ }
134
+
135
+ // Singleton instance for performance
136
+ const colorMapper = new MaterialColorMapper();
137
+
138
+ // Cache for resolved colors to improve performance
139
+ const colorCache = new Map<string, string>();
140
+
141
+ /**
142
+ * Clear the color cache
143
+ * Useful for testing or theme changes
144
+ */
145
+ export function clearColorCache(): void {
146
+ colorCache.clear();
147
+ }
148
+
149
+ /**
150
+ * Get text color from design tokens based on color variant
151
+ *
152
+ * @param color - Color variant or custom color string
153
+ * @param tokens - Design tokens containing color values
154
+ * @returns Resolved color string
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * const tokens = useAppDesignTokens();
159
+ * const textColor = getTextColor('textPrimary', tokens);
160
+ * ```
161
+ */
162
+ export function getTextColor(
163
+ color: ColorVariant | string | undefined,
164
+ tokens: DesignTokens,
165
+ ): string {
166
+ // Validate tokens parameter
167
+ if (!tokens || !tokens.colors) {
168
+ throw new Error('Invalid design tokens: tokens and tokens.colors are required');
169
+ }
170
+
171
+ if (!color) {
172
+ return tokens.colors.textPrimary;
173
+ }
174
+
175
+ // If it's a custom color string (not a variant), return as-is
176
+ if (typeof color === 'string' && !isColorVariant(color)) {
177
+ return color;
178
+ }
179
+
180
+ // Create cache key - use hash instead of JSON.stringify to prevent memory leaks
181
+ const cacheKey = `${color}_${Object.keys(tokens.colors).length}_${tokens.colors.textPrimary}`;
182
+
183
+ // Check cache first
184
+ if (colorCache.has(cacheKey)) {
185
+ return colorCache.get(cacheKey)!;
186
+ }
187
+
188
+ // Resolve color and cache it
189
+ const resolvedColor = colorMapper.getColor(color as ColorVariant, tokens);
190
+ colorCache.set(cacheKey, resolvedColor);
191
+
192
+ return resolvedColor;
193
+ }
194
+
195
+ /**
196
+ * Check if a string is a valid ColorVariant
197
+ * Uses Set for O(1) lookup performance
198
+ *
199
+ * @param value - String to check
200
+ * @returns True if value is a ColorVariant
201
+ */
202
+ function isColorVariant(value: string): value is ColorVariant {
203
+ return COLOR_VARIANT_SET.has(value);
204
+ }
205
+