@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.
- package/package.json +26 -19
- package/src/atoms/AtomicAvatar.tsx +161 -0
- package/src/atoms/AtomicButton.tsx +241 -0
- package/src/atoms/AtomicCard.tsx +84 -0
- package/src/atoms/AtomicChip.tsx +226 -0
- package/src/atoms/AtomicDatePicker.tsx +255 -0
- package/src/atoms/AtomicFab.tsx +99 -0
- package/src/atoms/AtomicIcon.tsx +149 -0
- package/src/atoms/AtomicInput.tsx +308 -0
- package/src/atoms/AtomicPicker.tsx +310 -0
- package/src/atoms/AtomicProgress.tsx +149 -0
- package/src/atoms/AtomicText.tsx +55 -0
- package/src/atoms/__tests__/AtomicButton.test.tsx +107 -0
- package/src/atoms/__tests__/AtomicIcon.test.tsx +110 -0
- package/src/atoms/__tests__/AtomicInput.test.tsx +195 -0
- package/src/atoms/datepicker/components/DatePickerButton.tsx +112 -0
- package/src/atoms/datepicker/components/DatePickerModal.tsx +143 -0
- package/src/atoms/fab/styles/fabStyles.ts +98 -0
- package/src/atoms/fab/types/index.ts +88 -0
- package/src/atoms/index.ts +70 -0
- package/src/atoms/input/hooks/useInputState.ts +63 -0
- package/src/atoms/input/styles/inputStylesHelper.ts +120 -0
- package/src/atoms/picker/components/PickerChips.tsx +57 -0
- package/src/atoms/picker/components/PickerModal.tsx +214 -0
- package/src/atoms/picker/styles/pickerStyles.ts +223 -0
- package/src/atoms/picker/types/index.ts +42 -0
- package/src/index.ts +133 -52
- package/src/molecules/ConfirmationModal.tsx +42 -0
- package/src/molecules/ConfirmationModalContent.tsx +87 -0
- package/src/molecules/ConfirmationModalMain.tsx +91 -0
- package/src/molecules/FormField.tsx +155 -0
- package/src/molecules/IconContainer.tsx +79 -0
- package/src/molecules/ListItem.tsx +35 -0
- package/src/molecules/ScreenHeader.tsx +171 -0
- package/src/molecules/SearchBar.tsx +198 -0
- package/src/molecules/confirmation-modal/components.tsx +94 -0
- package/src/molecules/confirmation-modal/index.ts +7 -0
- package/src/molecules/confirmation-modal/styles/confirmationModalStyles.ts +133 -0
- package/src/molecules/confirmation-modal/types/index.ts +41 -0
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +50 -0
- package/src/molecules/index.ts +19 -0
- package/src/molecules/listitem/index.ts +6 -0
- package/src/molecules/listitem/styles/listItemStyles.ts +37 -0
- package/src/molecules/listitem/types/index.ts +21 -0
- package/src/organisms/AppHeader.tsx +136 -0
- package/src/organisms/FormContainer.tsx +169 -0
- package/src/organisms/ScreenLayout.tsx +183 -0
- package/src/organisms/index.ts +31 -0
- package/src/responsive/config.ts +139 -0
- package/src/responsive/deviceDetection.ts +155 -0
- package/src/responsive/gridUtils.ts +79 -0
- package/src/responsive/index.ts +52 -0
- package/src/responsive/platformConstants.ts +98 -0
- package/src/responsive/responsive.ts +61 -0
- package/src/responsive/responsiveLayout.ts +137 -0
- package/src/responsive/responsiveSizing.ts +134 -0
- package/src/responsive/useResponsive.ts +140 -0
- package/src/responsive/validation.ts +158 -0
- package/src/theme/core/BaseTokens.ts +42 -0
- package/src/theme/core/ColorPalette.ts +29 -0
- package/src/theme/core/CustomColors.ts +122 -0
- package/src/theme/core/NavigationTheme.ts +72 -0
- package/src/theme/core/TokenFactory.ts +103 -0
- package/src/theme/core/colors/ColorUtils.ts +53 -0
- package/src/theme/core/colors/DarkColors.ts +146 -0
- package/src/theme/core/colors/LightColors.ts +146 -0
- package/src/theme/core/constants/DesignConstants.ts +31 -0
- package/src/theme/core/themes.ts +118 -0
- package/src/theme/core/tokens/BaseTokens.ts +144 -0
- package/src/theme/core/tokens/Borders.ts +43 -0
- package/src/theme/core/tokens/Sizes.ts +51 -0
- package/src/theme/core/tokens/Spacing.ts +38 -0
- package/src/theme/core/tokens/Typography.ts +143 -0
- package/src/theme/hooks/useAppDesignTokens.ts +45 -0
- package/src/theme/hooks/useCommonStyles.ts +248 -0
- package/src/theme/hooks/useThemedStyles.ts +68 -0
- package/src/theme/index.ts +94 -0
- package/src/theme/infrastructure/globalThemeStore.ts +69 -0
- package/src/theme/infrastructure/storage/ThemeStorage.ts +93 -0
- package/src/theme/infrastructure/stores/themeStore.ts +109 -0
- package/src/typography/__tests__/colorValidationUtils.test.ts +180 -0
- package/src/typography/__tests__/textColorUtils.test.ts +185 -0
- package/src/typography/__tests__/textStyleUtils.test.ts +168 -0
- package/src/typography/domain/entities/TypographyTypes.ts +88 -0
- package/src/typography/index.ts +53 -0
- package/src/typography/presentation/utils/colorValidationUtils.ts +133 -0
- package/src/typography/presentation/utils/textColorUtils.ts +205 -0
- 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
|
+
|