@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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Storage
|
|
3
|
+
* Persists theme preference using AsyncStorage
|
|
4
|
+
*
|
|
5
|
+
* CRITICAL: This is a standalone storage utility for theme package.
|
|
6
|
+
* Apps should use this for theme persistence.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
10
|
+
import type { ThemeMode } from '../../core/ColorPalette';
|
|
11
|
+
import { DESIGN_CONSTANTS } from '../../core/constants/DesignConstants';
|
|
12
|
+
|
|
13
|
+
const STORAGE_KEY = `${DESIGN_CONSTANTS.STORAGE_NAMESPACE}/mode`;
|
|
14
|
+
|
|
15
|
+
export class ThemeStorage {
|
|
16
|
+
/**
|
|
17
|
+
* Get stored theme mode
|
|
18
|
+
*/
|
|
19
|
+
static async getThemeMode(): Promise<ThemeMode | null> {
|
|
20
|
+
try {
|
|
21
|
+
const value = await AsyncStorage.getItem(STORAGE_KEY);
|
|
22
|
+
if (!value) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate theme mode value
|
|
27
|
+
if (value === 'light' || value === 'dark') {
|
|
28
|
+
return value as ThemeMode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (__DEV__) {
|
|
32
|
+
console.warn('[ThemeStorage] Invalid theme mode value stored:', value);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
console.error('[ThemeStorage] Error getting theme mode:', errorMessage);
|
|
39
|
+
}
|
|
40
|
+
// Return null instead of throwing to prevent app crashes
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save theme mode
|
|
47
|
+
*/
|
|
48
|
+
static async setThemeMode(mode: ThemeMode): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
// Validate input
|
|
51
|
+
if (!mode || (mode !== 'light' && mode !== 'dark')) {
|
|
52
|
+
throw new Error(`Invalid theme mode: ${mode}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await AsyncStorage.setItem(STORAGE_KEY, mode);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
58
|
+
if (__DEV__) {
|
|
59
|
+
console.error('[ThemeStorage] Error saving theme mode:', errorMessage);
|
|
60
|
+
}
|
|
61
|
+
// Re-throw validation errors but swallow storage errors to prevent app crashes
|
|
62
|
+
if (errorMessage.includes('Invalid theme mode')) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clear stored theme mode
|
|
70
|
+
*/
|
|
71
|
+
static async clearThemeMode(): Promise<void> {
|
|
72
|
+
try {
|
|
73
|
+
await AsyncStorage.removeItem(STORAGE_KEY);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
76
|
+
if (__DEV__) {
|
|
77
|
+
console.error('[ThemeStorage] Error clearing theme mode:', errorMessage);
|
|
78
|
+
}
|
|
79
|
+
// Don't throw - clearing storage is not critical
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme Store - Zustand State Management
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: NO Context Provider pattern - uses Zustand for global state
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - Zustand for state management (NOT Context API)
|
|
8
|
+
* - AsyncStorage for persistence via ThemeStorage
|
|
9
|
+
* - Automatic initialization on app launch
|
|
10
|
+
* - Syncs with design system global theme store
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { create } from 'zustand';
|
|
14
|
+
import { lightTheme, darkTheme, type Theme } from '../../core/themes';
|
|
15
|
+
import { ThemeStorage } from '../storage/ThemeStorage';
|
|
16
|
+
import { useDesignSystemTheme } from '../globalThemeStore';
|
|
17
|
+
import type { ThemeMode } from '../../core/ColorPalette';
|
|
18
|
+
|
|
19
|
+
interface ThemeState {
|
|
20
|
+
theme: Theme;
|
|
21
|
+
themeMode: ThemeMode;
|
|
22
|
+
isDark: boolean;
|
|
23
|
+
isInitialized: boolean;
|
|
24
|
+
setThemeMode: (mode: ThemeMode) => Promise<void>;
|
|
25
|
+
toggleTheme: () => Promise<void>;
|
|
26
|
+
initialize: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Theme Store - Global state management for theme
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { useTheme } from '@umituz/react-native-design-system-theme';
|
|
35
|
+
*
|
|
36
|
+
* const MyComponent = () => {
|
|
37
|
+
* const { theme, themeMode, setThemeMode, toggleTheme } = useTheme();
|
|
38
|
+
* // ...
|
|
39
|
+
* };
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const useTheme = create<ThemeState>()((set: any, get: any) => ({
|
|
43
|
+
theme: darkTheme,
|
|
44
|
+
themeMode: 'dark',
|
|
45
|
+
isDark: true,
|
|
46
|
+
isInitialized: false,
|
|
47
|
+
|
|
48
|
+
initialize: async () => {
|
|
49
|
+
try {
|
|
50
|
+
const savedMode = await ThemeStorage.getThemeMode();
|
|
51
|
+
if (savedMode) {
|
|
52
|
+
const theme = savedMode === 'light' ? lightTheme : darkTheme;
|
|
53
|
+
set({
|
|
54
|
+
themeMode: savedMode,
|
|
55
|
+
theme,
|
|
56
|
+
isDark: savedMode === 'dark',
|
|
57
|
+
isInitialized: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Sync with design system global theme
|
|
61
|
+
useDesignSystemTheme.getState().setThemeMode(savedMode);
|
|
62
|
+
} else {
|
|
63
|
+
// No saved mode - use default 'dark' and sync to design system store
|
|
64
|
+
set({ isInitialized: true });
|
|
65
|
+
// Ensure design system store is synced even if no saved mode exists
|
|
66
|
+
useDesignSystemTheme.getState().setThemeMode('dark');
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
/* eslint-disable-next-line no-console */
|
|
70
|
+
if (__DEV__) console.error('[ThemeStore] Initialization error:', error);
|
|
71
|
+
// Silent failure - still mark as initialized to prevent blocking
|
|
72
|
+
set({ isInitialized: true });
|
|
73
|
+
// Ensure design system store is synced even on error
|
|
74
|
+
useDesignSystemTheme.getState().setThemeMode('dark');
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
setThemeMode: async (mode: ThemeMode) => {
|
|
79
|
+
try {
|
|
80
|
+
const theme = mode === 'light' ? lightTheme : darkTheme;
|
|
81
|
+
|
|
82
|
+
set({
|
|
83
|
+
themeMode: mode,
|
|
84
|
+
theme,
|
|
85
|
+
isDark: mode === 'dark',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await ThemeStorage.setThemeMode(mode);
|
|
89
|
+
|
|
90
|
+
// Sync with design system global theme
|
|
91
|
+
useDesignSystemTheme.getState().setThemeMode(mode);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
/* eslint-disable-next-line no-console */
|
|
94
|
+
if (__DEV__) console.error('[ThemeStore] Error setting theme mode:', error);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
toggleTheme: async () => {
|
|
99
|
+
const { themeMode, setThemeMode } = get();
|
|
100
|
+
const newMode: ThemeMode = themeMode === 'light' ? 'dark' : 'light';
|
|
101
|
+
await setThemeMode(newMode);
|
|
102
|
+
},
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for color validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
isValidHexColor,
|
|
7
|
+
isValidRgbColor,
|
|
8
|
+
isValidHslColor,
|
|
9
|
+
isValidNamedColor,
|
|
10
|
+
isValidColor,
|
|
11
|
+
getColorFormat,
|
|
12
|
+
normalizeColor,
|
|
13
|
+
} from '../presentation/utils/colorValidationUtils';
|
|
14
|
+
|
|
15
|
+
describe('isValidHexColor', () => {
|
|
16
|
+
test('should return true for valid 3-digit hex colors', () => {
|
|
17
|
+
expect(isValidHexColor('#fff')).toBe(true);
|
|
18
|
+
expect(isValidHexColor('#000')).toBe(true);
|
|
19
|
+
expect(isValidHexColor('#abc')).toBe(true);
|
|
20
|
+
expect(isValidHexColor('#123')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should return true for valid 6-digit hex colors', () => {
|
|
24
|
+
expect(isValidHexColor('#ffffff')).toBe(true);
|
|
25
|
+
expect(isValidHexColor('#000000')).toBe(true);
|
|
26
|
+
expect(isValidHexColor('#abcdef')).toBe(true);
|
|
27
|
+
expect(isValidHexColor('#123456')).toBe(true);
|
|
28
|
+
expect(isValidHexColor('#FF5733')).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should return false for invalid hex colors', () => {
|
|
32
|
+
expect(isValidHexColor('#ff')).toBe(false);
|
|
33
|
+
expect(isValidHexColor('#ffff')).toBe(false);
|
|
34
|
+
expect(isValidHexColor('#ggg')).toBe(false);
|
|
35
|
+
expect(isValidHexColor('ffffff')).toBe(false);
|
|
36
|
+
expect(isValidHexColor('#12345')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('isValidRgbColor', () => {
|
|
41
|
+
test('should return true for valid RGB colors', () => {
|
|
42
|
+
expect(isValidRgbColor('rgb(255, 0, 0)')).toBe(true);
|
|
43
|
+
expect(isValidRgbColor('rgb(0, 255, 0)')).toBe(true);
|
|
44
|
+
expect(isValidRgbColor('rgb(0, 0, 255)')).toBe(true);
|
|
45
|
+
expect(isValidRgbColor('rgb(128, 128, 128)')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should return true for valid RGBA colors', () => {
|
|
49
|
+
expect(isValidRgbColor('rgba(255, 0, 0, 0.5)')).toBe(true);
|
|
50
|
+
expect(isValidRgbColor('rgba(0, 255, 0, 1)')).toBe(true);
|
|
51
|
+
expect(isValidRgbColor('rgba(0, 0, 255, 0)')).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should return true for RGB colors with percentages', () => {
|
|
55
|
+
expect(isValidRgbColor('rgb(100%, 0%, 0%)')).toBe(true);
|
|
56
|
+
expect(isValidRgbColor('rgb(50%, 50%, 50%)')).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should return false for invalid RGB colors', () => {
|
|
60
|
+
expect(isValidRgbColor('rgb(256, 0, 0)')).toBe(false);
|
|
61
|
+
expect(isValidRgbColor('rgb(255, -1, 0)')).toBe(false);
|
|
62
|
+
expect(isValidRgbColor('rgb(255, 0)')).toBe(false);
|
|
63
|
+
expect(isValidRgbColor('255, 0, 0')).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('isValidHslColor', () => {
|
|
68
|
+
test('should return true for valid HSL colors', () => {
|
|
69
|
+
expect(isValidHslColor('hsl(0, 100%, 50%)')).toBe(true);
|
|
70
|
+
expect(isValidHslColor('hsl(120, 100%, 50%)')).toBe(true);
|
|
71
|
+
expect(isValidHslColor('hsl(240, 100%, 50%)')).toBe(true);
|
|
72
|
+
expect(isValidHslColor('hsl(180, 50%, 50%)')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should return true for valid HSLA colors', () => {
|
|
76
|
+
expect(isValidHslColor('hsla(0, 100%, 50%, 0.5)')).toBe(true);
|
|
77
|
+
expect(isValidHslColor('hsla(120, 100%, 50%, 1)')).toBe(true);
|
|
78
|
+
expect(isValidHslColor('hsla(240, 100%, 50%, 0)')).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('should return false for invalid HSL colors', () => {
|
|
82
|
+
expect(isValidHslColor('hsl(361, 100%, 50%)')).toBe(false);
|
|
83
|
+
expect(isValidHslColor('hsl(120, 101%, 50%)')).toBe(false);
|
|
84
|
+
expect(isValidHslColor('hsl(120, 100%, 101%)')).toBe(false);
|
|
85
|
+
expect(isValidHslColor('hsl(120, 100%)')).toBe(false);
|
|
86
|
+
expect(isValidHslColor('120, 100%, 50%')).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('isValidNamedColor', () => {
|
|
91
|
+
test('should return true for valid CSS color names', () => {
|
|
92
|
+
expect(isValidNamedColor('red')).toBe(true);
|
|
93
|
+
expect(isValidNamedColor('blue')).toBe(true);
|
|
94
|
+
expect(isValidNamedColor('green')).toBe(true);
|
|
95
|
+
expect(isValidNamedColor('transparent')).toBe(true);
|
|
96
|
+
expect(isValidNamedColor('currentColor')).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should return false for invalid color names', () => {
|
|
100
|
+
expect(isValidNamedColor('notacolor')).toBe(false);
|
|
101
|
+
expect(isValidNamedColor('color123')).toBe(false);
|
|
102
|
+
expect(isValidNamedColor('')).toBe(false);
|
|
103
|
+
expect(isValidNamedColor('red-blue')).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('isValidColor', () => {
|
|
108
|
+
test('should return true for any valid color format', () => {
|
|
109
|
+
expect(isValidColor('#fff')).toBe(true);
|
|
110
|
+
expect(isValidColor('#ffffff')).toBe(true);
|
|
111
|
+
expect(isValidColor('rgb(255, 0, 0)')).toBe(true);
|
|
112
|
+
expect(isValidColor('rgba(255, 0, 0, 0.5)')).toBe(true);
|
|
113
|
+
expect(isValidColor('hsl(0, 100%, 50%)')).toBe(true);
|
|
114
|
+
expect(isValidColor('hsla(0, 100%, 50%, 0.5)')).toBe(true);
|
|
115
|
+
expect(isValidColor('red')).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should return false for invalid colors', () => {
|
|
119
|
+
expect(isValidColor('invalid')).toBe(false);
|
|
120
|
+
expect(isValidColor('#ggg')).toBe(false);
|
|
121
|
+
expect(isValidColor('rgb(256, 0, 0)')).toBe(false);
|
|
122
|
+
expect(isValidColor('')).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('getColorFormat', () => {
|
|
127
|
+
test('should return correct format for hex colors', () => {
|
|
128
|
+
expect(getColorFormat('#fff')).toBe('hex');
|
|
129
|
+
expect(getColorFormat('#ffffff')).toBe('hex');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should return correct format for RGB colors', () => {
|
|
133
|
+
expect(getColorFormat('rgb(255, 0, 0)')).toBe('rgb');
|
|
134
|
+
expect(getColorFormat('rgba(255, 0, 0, 0.5)')).toBe('rgb');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should return correct format for HSL colors', () => {
|
|
138
|
+
expect(getColorFormat('hsl(0, 100%, 50%)')).toBe('hsl');
|
|
139
|
+
expect(getColorFormat('hsla(0, 100%, 50%, 0.5)')).toBe('hsl');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('should return correct format for named colors', () => {
|
|
143
|
+
expect(getColorFormat('red')).toBe('named');
|
|
144
|
+
expect(getColorFormat('blue')).toBe('named');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should return unknown for invalid colors', () => {
|
|
148
|
+
expect(getColorFormat('invalid')).toBe('unknown');
|
|
149
|
+
expect(getColorFormat('')).toBe('unknown');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('normalizeColor', () => {
|
|
154
|
+
test('should convert 3-digit hex to 6-digit', () => {
|
|
155
|
+
expect(normalizeColor('#fff')).toBe('#ffffff');
|
|
156
|
+
expect(normalizeColor('#abc')).toBe('#aabbcc');
|
|
157
|
+
expect(normalizeColor('#123')).toBe('#112233');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('should keep 6-digit hex unchanged', () => {
|
|
161
|
+
expect(normalizeColor('#ffffff')).toBe('#ffffff');
|
|
162
|
+
expect(normalizeColor('#abcdef')).toBe('#abcdef');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('should convert to lowercase', () => {
|
|
166
|
+
expect(normalizeColor('#FFFFFF')).toBe('#ffffff');
|
|
167
|
+
expect(normalizeColor('#ABCDEF')).toBe('#abcdef');
|
|
168
|
+
expect(normalizeColor('RED')).toBe('red');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should return unchanged for invalid colors', () => {
|
|
172
|
+
expect(normalizeColor('invalid')).toBe('invalid');
|
|
173
|
+
expect(normalizeColor('')).toBe('');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should return unchanged for non-hex colors', () => {
|
|
177
|
+
expect(normalizeColor('rgb(255, 0, 0)')).toBe('rgb(255, 0, 0)');
|
|
178
|
+
expect(normalizeColor('hsl(0, 100%, 50%)')).toBe('hsl(0, 100%, 50%)');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for text color utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getTextColor } from '../presentation/utils/textColorUtils';
|
|
6
|
+
import type { ColorVariant } from '../domain/entities/TypographyTypes';
|
|
7
|
+
|
|
8
|
+
import type { DesignTokens } from '@umituz/react-native-design-system-theme';
|
|
9
|
+
|
|
10
|
+
// Mock design tokens for testing
|
|
11
|
+
const mockTokens: DesignTokens = {
|
|
12
|
+
colors: {
|
|
13
|
+
textPrimary: '#000000',
|
|
14
|
+
textSecondary: '#666666',
|
|
15
|
+
textTertiary: '#999999',
|
|
16
|
+
textDisabled: '#CCCCCC',
|
|
17
|
+
textInverse: '#FFFFFF',
|
|
18
|
+
onSurface: '#000000',
|
|
19
|
+
onBackground: '#000000',
|
|
20
|
+
onPrimary: '#FFFFFF',
|
|
21
|
+
onSecondary: '#FFFFFF',
|
|
22
|
+
onSuccess: '#FFFFFF',
|
|
23
|
+
onError: '#FFFFFF',
|
|
24
|
+
onWarning: '#000000',
|
|
25
|
+
onInfo: '#FFFFFF',
|
|
26
|
+
success: '#4CAF50',
|
|
27
|
+
error: '#F44336',
|
|
28
|
+
warning: '#FF9800',
|
|
29
|
+
info: '#2196F3',
|
|
30
|
+
primary: '#2196F3',
|
|
31
|
+
secondary: '#FF9800',
|
|
32
|
+
tertiary: '#9C27B0',
|
|
33
|
+
surface: '#FFFFFF',
|
|
34
|
+
surfaceVariant: '#F5F5F5',
|
|
35
|
+
background: '#FFFFFF',
|
|
36
|
+
},
|
|
37
|
+
typography: {
|
|
38
|
+
displayLarge: { fontSize: 57, fontWeight: '400' },
|
|
39
|
+
displayMedium: { fontSize: 45, fontWeight: '400' },
|
|
40
|
+
displaySmall: { fontSize: 36, fontWeight: '400' },
|
|
41
|
+
headlineLarge: { fontSize: 32, fontWeight: '400' },
|
|
42
|
+
headlineMedium: { fontSize: 28, fontWeight: '400' },
|
|
43
|
+
headlineSmall: { fontSize: 24, fontWeight: '400' },
|
|
44
|
+
titleLarge: { fontSize: 22, fontWeight: '500' },
|
|
45
|
+
titleMedium: { fontSize: 16, fontWeight: '500' },
|
|
46
|
+
titleSmall: { fontSize: 14, fontWeight: '500' },
|
|
47
|
+
bodyLarge: { fontSize: 16, fontWeight: '400' },
|
|
48
|
+
bodyMedium: { fontSize: 14, fontWeight: '400' },
|
|
49
|
+
bodySmall: { fontSize: 12, fontWeight: '400' },
|
|
50
|
+
labelLarge: { fontSize: 14, fontWeight: '500' },
|
|
51
|
+
labelMedium: { fontSize: 12, fontWeight: '500' },
|
|
52
|
+
labelSmall: { fontSize: 11, fontWeight: '500' },
|
|
53
|
+
},
|
|
54
|
+
spacing: {},
|
|
55
|
+
shadows: {},
|
|
56
|
+
borderRadius: {},
|
|
57
|
+
iconSizes: {},
|
|
58
|
+
opacity: {},
|
|
59
|
+
avatarSizes: {},
|
|
60
|
+
borders: {},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
describe('getTextColor', () => {
|
|
66
|
+
test('should throw error when tokens is null', () => {
|
|
67
|
+
expect(() => getTextColor('textPrimary', null as any)).toThrow(
|
|
68
|
+
'Invalid design tokens: tokens and tokens.colors are required'
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should throw error when tokens is undefined', () => {
|
|
73
|
+
expect(() => getTextColor('textPrimary', undefined as any)).toThrow(
|
|
74
|
+
'Invalid design tokens: tokens and tokens.colors are required'
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should throw error when tokens.colors is null', () => {
|
|
79
|
+
expect(() => getTextColor('textPrimary', { colors: null } as any)).toThrow(
|
|
80
|
+
'Invalid design tokens: tokens and tokens.colors are required'
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should return default textPrimary when color is undefined', () => {
|
|
85
|
+
const result = getTextColor(undefined, mockTokens);
|
|
86
|
+
expect(result).toBe('#000000');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should return custom color as-is when not a variant', () => {
|
|
90
|
+
const customColor = '#FF5722';
|
|
91
|
+
const result = getTextColor(customColor, mockTokens);
|
|
92
|
+
expect(result).toBe(customColor);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should map textPrimary variant correctly', () => {
|
|
96
|
+
const result = getTextColor('textPrimary', mockTokens);
|
|
97
|
+
expect(result).toBe('#000000');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('should map textSecondary variant correctly', () => {
|
|
101
|
+
const result = getTextColor('textSecondary', mockTokens);
|
|
102
|
+
expect(result).toBe('#666666');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should map semantic colors correctly', () => {
|
|
106
|
+
expect(getTextColor('success', mockTokens)).toBe('#4CAF50');
|
|
107
|
+
expect(getTextColor('error', mockTokens)).toBe('#F44336');
|
|
108
|
+
expect(getTextColor('warning', mockTokens)).toBe('#FF9800');
|
|
109
|
+
expect(getTextColor('info', mockTokens)).toBe('#2196F3');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should map on-colors correctly', () => {
|
|
113
|
+
expect(getTextColor('onPrimary', mockTokens)).toBe('#FFFFFF');
|
|
114
|
+
expect(getTextColor('onError', mockTokens)).toBe('#FFFFFF');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should handle legacy variants correctly', () => {
|
|
118
|
+
expect(getTextColor('primary', mockTokens)).toBe('#000000'); // Maps to textPrimary
|
|
119
|
+
expect(getTextColor('secondary', mockTokens)).toBe('#666666'); // Maps to textSecondary
|
|
120
|
+
expect(getTextColor('surfaceVariant', mockTokens)).toBe('#666666'); // Maps to textSecondary
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('should handle empty string', () => {
|
|
124
|
+
const result = getTextColor('', mockTokens);
|
|
125
|
+
expect(result).toBe('#000000');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should handle hex color strings', () => {
|
|
129
|
+
const hexColor = '#RRGGBB';
|
|
130
|
+
const result = getTextColor(hexColor, mockTokens);
|
|
131
|
+
expect(result).toBe(hexColor);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('should handle rgb color strings', () => {
|
|
135
|
+
const rgbColor = 'rgb(255, 0, 0)';
|
|
136
|
+
const result = getTextColor(rgbColor, mockTokens);
|
|
137
|
+
expect(result).toBe(rgbColor);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('should return custom color as-is when not a variant', () => {
|
|
141
|
+
const customColor = 'unknownVariant';
|
|
142
|
+
const result = getTextColor(customColor, mockTokens);
|
|
143
|
+
expect(result).toBe(customColor);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('ColorVariant validation', () => {
|
|
152
|
+
test('should validate all defined color variants', () => {
|
|
153
|
+
const validVariants: ColorVariant[] = [
|
|
154
|
+
'textPrimary',
|
|
155
|
+
'textSecondary',
|
|
156
|
+
'textTertiary',
|
|
157
|
+
'textDisabled',
|
|
158
|
+
'textInverse',
|
|
159
|
+
'onSurface',
|
|
160
|
+
'onBackground',
|
|
161
|
+
'onPrimary',
|
|
162
|
+
'onSecondary',
|
|
163
|
+
'onSuccess',
|
|
164
|
+
'onError',
|
|
165
|
+
'onWarning',
|
|
166
|
+
'onInfo',
|
|
167
|
+
'success',
|
|
168
|
+
'error',
|
|
169
|
+
'warning',
|
|
170
|
+
'info',
|
|
171
|
+
'primary',
|
|
172
|
+
'secondary',
|
|
173
|
+
'tertiary',
|
|
174
|
+
'disabled',
|
|
175
|
+
'inverse',
|
|
176
|
+
'surfaceVariant',
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
validVariants.forEach(variant => {
|
|
180
|
+
const result = getTextColor(variant, mockTokens);
|
|
181
|
+
expect(typeof result).toBe('string');
|
|
182
|
+
expect(result.length).toBeGreaterThan(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|