@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,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Centralized validation for all responsive utility functions.
|
|
5
|
+
* Ensures type safety and prevents runtime errors.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { VALIDATION_CONSTRAINTS } from './config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom error class for responsive utilities
|
|
12
|
+
*/
|
|
13
|
+
export class ResponsiveValidationError extends Error {
|
|
14
|
+
constructor(message: string) {
|
|
15
|
+
super(`[Responsive] ${message}`);
|
|
16
|
+
this.name = 'ResponsiveValidationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates a numeric input parameter
|
|
22
|
+
* @param value - The value to validate
|
|
23
|
+
* @param paramName - Parameter name for error messages
|
|
24
|
+
* @param min - Minimum allowed value
|
|
25
|
+
* @param max - Maximum allowed value
|
|
26
|
+
* @throws ResponsiveValidationError if validation fails
|
|
27
|
+
*/
|
|
28
|
+
export const validateNumber = (
|
|
29
|
+
value: number | undefined,
|
|
30
|
+
paramName: string,
|
|
31
|
+
min: number = VALIDATION_CONSTRAINTS.MIN_BASE_SIZE,
|
|
32
|
+
max: number = VALIDATION_CONSTRAINTS.MAX_BASE_SIZE
|
|
33
|
+
): number => {
|
|
34
|
+
if (value === undefined || value === null) {
|
|
35
|
+
throw new ResponsiveValidationError(`${paramName} is required`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof value !== 'number' || isNaN(value)) {
|
|
39
|
+
throw new ResponsiveValidationError(`${paramName} must be a valid number`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!isFinite(value)) {
|
|
43
|
+
throw new ResponsiveValidationError(`${paramName} must be a finite number`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (value < min) {
|
|
47
|
+
throw new ResponsiveValidationError(`${paramName} must be at least ${min}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (value > max) {
|
|
51
|
+
throw new ResponsiveValidationError(`${paramName} must be at most ${max}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates font size input
|
|
59
|
+
* @param fontSize - Font size to validate
|
|
60
|
+
* @param paramName - Parameter name for error messages
|
|
61
|
+
* @returns Validated font size
|
|
62
|
+
*/
|
|
63
|
+
export const validateFontSize = (fontSize: number, paramName: string = 'fontSize'): number => {
|
|
64
|
+
return validateNumber(
|
|
65
|
+
fontSize,
|
|
66
|
+
paramName,
|
|
67
|
+
VALIDATION_CONSTRAINTS.MIN_BASE_FONT_SIZE,
|
|
68
|
+
VALIDATION_CONSTRAINTS.MAX_BASE_FONT_SIZE
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validates screen dimensions
|
|
74
|
+
* @param width - Screen width
|
|
75
|
+
* @param height - Screen height
|
|
76
|
+
* @throws ResponsiveValidationError if validation fails
|
|
77
|
+
*/
|
|
78
|
+
export const validateScreenDimensions = (width: number, height: number): void => {
|
|
79
|
+
validateNumber(
|
|
80
|
+
width,
|
|
81
|
+
'width',
|
|
82
|
+
VALIDATION_CONSTRAINTS.MIN_SCREEN_DIMENSION,
|
|
83
|
+
VALIDATION_CONSTRAINTS.MAX_SCREEN_DIMENSION
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
validateNumber(
|
|
87
|
+
height,
|
|
88
|
+
'height',
|
|
89
|
+
VALIDATION_CONSTRAINTS.MIN_SCREEN_DIMENSION,
|
|
90
|
+
VALIDATION_CONSTRAINTS.MAX_SCREEN_DIMENSION
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validates safe area insets
|
|
96
|
+
* @param insets - Safe area insets object
|
|
97
|
+
* @throws ResponsiveValidationError if validation fails
|
|
98
|
+
*/
|
|
99
|
+
export const validateSafeAreaInsets = (insets: {
|
|
100
|
+
top?: number;
|
|
101
|
+
bottom?: number;
|
|
102
|
+
left?: number;
|
|
103
|
+
right?: number;
|
|
104
|
+
}): void => {
|
|
105
|
+
if (!insets || typeof insets !== 'object') {
|
|
106
|
+
throw new ResponsiveValidationError('Safe area insets must be an object');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const { top = 0, bottom = 0, left = 0, right = 0 } = insets;
|
|
110
|
+
|
|
111
|
+
validateNumber(top, 'insets.top', 0, 1000);
|
|
112
|
+
validateNumber(bottom, 'insets.bottom', 0, 1000);
|
|
113
|
+
validateNumber(left, 'insets.left', 0, 1000);
|
|
114
|
+
validateNumber(right, 'insets.right', 0, 1000);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Validates grid column parameters
|
|
119
|
+
* @param mobileColumns - Number of columns for mobile
|
|
120
|
+
* @param tabletColumns - Number of columns for tablet
|
|
121
|
+
* @throws ResponsiveValidationError if validation fails
|
|
122
|
+
*/
|
|
123
|
+
export const validateGridColumns = (
|
|
124
|
+
mobileColumns?: number,
|
|
125
|
+
tabletColumns?: number
|
|
126
|
+
): void => {
|
|
127
|
+
if (mobileColumns !== undefined) {
|
|
128
|
+
validateNumber(mobileColumns, 'mobileColumns', 1, 20);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (tabletColumns !== undefined) {
|
|
132
|
+
validateNumber(tabletColumns, 'tabletColumns', 1, 20);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clamps a value between min and max bounds
|
|
138
|
+
* @param value - Value to clamp
|
|
139
|
+
* @param min - Minimum value
|
|
140
|
+
* @param max - Maximum value
|
|
141
|
+
* @returns Clamped value
|
|
142
|
+
*/
|
|
143
|
+
export const clamp = (value: number, min: number, max: number): number => {
|
|
144
|
+
return Math.min(Math.max(value, min), max);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Safely calculates a percentage of a value
|
|
149
|
+
* @param value - Base value
|
|
150
|
+
* @param percentage - Percentage (0-1)
|
|
151
|
+
* @returns Calculated percentage
|
|
152
|
+
*/
|
|
153
|
+
export const safePercentage = (value: number, percentage: number): number => {
|
|
154
|
+
const validatedValue = validateNumber(value, 'value', 0, Infinity);
|
|
155
|
+
const validatedPercentage = clamp(percentage, 0, 1);
|
|
156
|
+
|
|
157
|
+
return validatedValue * validatedPercentage;
|
|
158
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BASE TOKENS - Main Export
|
|
3
|
+
*
|
|
4
|
+
* Aggregates all token modules into BASE_TOKENS
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spacing } from './tokens/Spacing';
|
|
8
|
+
import { typography } from './tokens/Typography';
|
|
9
|
+
import { borders } from './tokens/Borders';
|
|
10
|
+
import { iconSizes, opacity, avatarSizes, sizes } from './tokens/Sizes';
|
|
11
|
+
import type { BaseTokens } from './tokens/BaseTokens';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* BASE_TOKENS - Static design tokens
|
|
15
|
+
* These values don't change with theme (light/dark)
|
|
16
|
+
*/
|
|
17
|
+
export const BASE_TOKENS: BaseTokens = {
|
|
18
|
+
spacing,
|
|
19
|
+
typography,
|
|
20
|
+
borders,
|
|
21
|
+
iconSizes,
|
|
22
|
+
opacity,
|
|
23
|
+
avatarSizes,
|
|
24
|
+
sizes,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Convenience exports
|
|
28
|
+
export { spacing, typography, borders, iconSizes, opacity, avatarSizes, sizes };
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Type exports
|
|
33
|
+
export type {
|
|
34
|
+
BaseTokens,
|
|
35
|
+
Spacing,
|
|
36
|
+
Typography,
|
|
37
|
+
Borders,
|
|
38
|
+
IconSizes,
|
|
39
|
+
Opacity,
|
|
40
|
+
AvatarSizes,
|
|
41
|
+
ComponentSizes
|
|
42
|
+
} from './tokens/BaseTokens';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COLOR PALETTE - Main Export
|
|
3
|
+
*
|
|
4
|
+
* Aggregates all color modules and provides theme utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { lightColors } from './colors/LightColors';
|
|
8
|
+
import { darkColors } from './colors/DarkColors';
|
|
9
|
+
import { withAlpha, isValidHexColor } from './colors/ColorUtils';
|
|
10
|
+
|
|
11
|
+
export type ColorPalette = typeof lightColors;
|
|
12
|
+
export type ThemeMode = 'light' | 'dark';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get color palette for specific theme mode
|
|
16
|
+
* @param mode - 'light' or 'dark'
|
|
17
|
+
* @returns Color palette object
|
|
18
|
+
*/
|
|
19
|
+
export const getColorPalette = (mode: ThemeMode): ColorPalette => {
|
|
20
|
+
return mode === 'dark' ? darkColors : lightColors;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Export all colors and utilities
|
|
24
|
+
export {
|
|
25
|
+
lightColors,
|
|
26
|
+
darkColors,
|
|
27
|
+
withAlpha,
|
|
28
|
+
isValidHexColor
|
|
29
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Colors Types
|
|
3
|
+
*
|
|
4
|
+
* Types for custom theme color overrides
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ColorPalette } from './ColorPalette';
|
|
8
|
+
import { isValidHexColor } from './colors/ColorUtils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Custom theme colors that can override default colors
|
|
12
|
+
*/
|
|
13
|
+
export interface CustomThemeColors {
|
|
14
|
+
primary?: string;
|
|
15
|
+
primaryLight?: string;
|
|
16
|
+
primaryDark?: string;
|
|
17
|
+
secondary?: string;
|
|
18
|
+
secondaryLight?: string;
|
|
19
|
+
secondaryDark?: string;
|
|
20
|
+
accent?: string;
|
|
21
|
+
accentLight?: string;
|
|
22
|
+
accentDark?: string;
|
|
23
|
+
buttonPrimary?: string;
|
|
24
|
+
buttonSecondary?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate custom colors object
|
|
29
|
+
* @param customColors - Custom colors to validate
|
|
30
|
+
* @returns true if all colors are valid hex format
|
|
31
|
+
*/
|
|
32
|
+
export const validateCustomColors = (customColors: CustomThemeColors): boolean => {
|
|
33
|
+
const colorValues = Object.values(customColors).filter(Boolean) as string[];
|
|
34
|
+
|
|
35
|
+
for (const color of colorValues) {
|
|
36
|
+
if (!isValidHexColor(color)) {
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
console.warn('[validateCustomColors] Invalid hex color:', color);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Apply custom colors to color palette
|
|
49
|
+
* @param palette - Base color palette
|
|
50
|
+
* @param customColors - Custom colors to apply
|
|
51
|
+
* @returns Color palette with custom colors applied
|
|
52
|
+
*/
|
|
53
|
+
export const applyCustomColors = (
|
|
54
|
+
palette: ColorPalette,
|
|
55
|
+
customColors?: CustomThemeColors,
|
|
56
|
+
): ColorPalette => {
|
|
57
|
+
if (!customColors) {
|
|
58
|
+
return palette;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Validate custom colors
|
|
62
|
+
if (!validateCustomColors(customColors)) {
|
|
63
|
+
if (__DEV__) {
|
|
64
|
+
console.error('[applyCustomColors] Invalid custom colors provided, using defaults');
|
|
65
|
+
}
|
|
66
|
+
return palette;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const result: Partial<ColorPalette> = {
|
|
70
|
+
...palette,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Apply custom primary colors
|
|
74
|
+
if (customColors.primary) {
|
|
75
|
+
result.primary = customColors.primary;
|
|
76
|
+
if (!customColors.buttonPrimary) {
|
|
77
|
+
result.buttonPrimary = customColors.primary;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (customColors.primaryLight) {
|
|
81
|
+
result.primaryLight = customColors.primaryLight;
|
|
82
|
+
}
|
|
83
|
+
if (customColors.primaryDark) {
|
|
84
|
+
result.primaryDark = customColors.primaryDark;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Apply custom secondary colors
|
|
88
|
+
if (customColors.secondary) {
|
|
89
|
+
result.secondary = customColors.secondary;
|
|
90
|
+
if (!customColors.buttonSecondary) {
|
|
91
|
+
result.buttonSecondary = customColors.secondary;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (customColors.secondaryLight) {
|
|
95
|
+
result.secondaryLight = customColors.secondaryLight;
|
|
96
|
+
}
|
|
97
|
+
if (customColors.secondaryDark) {
|
|
98
|
+
result.secondaryDark = customColors.secondaryDark;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Apply custom accent colors
|
|
102
|
+
if (customColors.accent) {
|
|
103
|
+
result.accent = customColors.accent;
|
|
104
|
+
}
|
|
105
|
+
if (customColors.accentLight) {
|
|
106
|
+
result.accentLight = customColors.accentLight;
|
|
107
|
+
}
|
|
108
|
+
if (customColors.accentDark) {
|
|
109
|
+
result.accentDark = customColors.accentDark;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Apply custom button colors (override primary/secondary if set)
|
|
113
|
+
if (customColors.buttonPrimary) {
|
|
114
|
+
result.buttonPrimary = customColors.buttonPrimary;
|
|
115
|
+
}
|
|
116
|
+
if (customColors.buttonSecondary) {
|
|
117
|
+
result.buttonSecondary = customColors.buttonSecondary;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result as ColorPalette;
|
|
121
|
+
};
|
|
122
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ColorPalette, ThemeMode } from "./ColorPalette";
|
|
2
|
+
import type { ExtendedColorPalette } from "./themes";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Font weight type compatible with React Navigation v7
|
|
6
|
+
*/
|
|
7
|
+
type FontWeight =
|
|
8
|
+
| "normal"
|
|
9
|
+
| "bold"
|
|
10
|
+
| "100"
|
|
11
|
+
| "200"
|
|
12
|
+
| "300"
|
|
13
|
+
| "400"
|
|
14
|
+
| "500"
|
|
15
|
+
| "600"
|
|
16
|
+
| "700"
|
|
17
|
+
| "800"
|
|
18
|
+
| "900";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Font configuration for navigation theme
|
|
22
|
+
*/
|
|
23
|
+
interface FontConfig {
|
|
24
|
+
fontFamily: string;
|
|
25
|
+
fontWeight: FontWeight;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Navigation theme type compatible with React Navigation v7
|
|
30
|
+
*/
|
|
31
|
+
export interface NavigationTheme {
|
|
32
|
+
dark: boolean;
|
|
33
|
+
colors: {
|
|
34
|
+
primary: string;
|
|
35
|
+
background: string;
|
|
36
|
+
card: string;
|
|
37
|
+
text: string;
|
|
38
|
+
border: string;
|
|
39
|
+
notification: string;
|
|
40
|
+
};
|
|
41
|
+
fonts: {
|
|
42
|
+
regular: FontConfig;
|
|
43
|
+
medium: FontConfig;
|
|
44
|
+
bold: FontConfig;
|
|
45
|
+
heavy: FontConfig;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a React Navigation theme from design system colors
|
|
51
|
+
* Compatible with React Navigation v7+
|
|
52
|
+
*/
|
|
53
|
+
export const createNavigationTheme = (
|
|
54
|
+
colors: ColorPalette | ExtendedColorPalette,
|
|
55
|
+
themeMode: ThemeMode
|
|
56
|
+
): NavigationTheme => ({
|
|
57
|
+
dark: themeMode === "dark",
|
|
58
|
+
colors: {
|
|
59
|
+
primary: colors.primary,
|
|
60
|
+
background: colors.backgroundPrimary,
|
|
61
|
+
card: colors.surface,
|
|
62
|
+
text: colors.textPrimary,
|
|
63
|
+
border: colors.borderLight,
|
|
64
|
+
notification: colors.error,
|
|
65
|
+
},
|
|
66
|
+
fonts: {
|
|
67
|
+
regular: { fontFamily: "System", fontWeight: "400" },
|
|
68
|
+
medium: { fontFamily: "System", fontWeight: "500" },
|
|
69
|
+
bold: { fontFamily: "System", fontWeight: "700" },
|
|
70
|
+
heavy: { fontFamily: "System", fontWeight: "800" },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TOKEN FACTORY - THEME INJECTION LOGIC
|
|
3
|
+
*
|
|
4
|
+
* ✅ Factory Pattern for creating complete design tokens
|
|
5
|
+
* ✅ Combines static tokens (BaseTokens) + dynamic colors (ColorPalette)
|
|
6
|
+
* ✅ Type-safe token generation
|
|
7
|
+
* ✅ Zero duplication - SINGLE SOURCE OF TRUTH
|
|
8
|
+
*
|
|
9
|
+
* @module TokenFactory
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { BASE_TOKENS } from './BaseTokens';
|
|
13
|
+
import { getColorPalette, withAlpha, type ThemeMode, type ColorPalette } from './ColorPalette';
|
|
14
|
+
import { applyCustomColors, type CustomThemeColors } from './CustomColors';
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// DESIGN TOKENS TYPE
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Complete design tokens shape
|
|
22
|
+
* Combines static tokens (spacing, typography, borders) + dynamic colors
|
|
23
|
+
*/
|
|
24
|
+
export type DesignTokens = {
|
|
25
|
+
colors: ColorPalette;
|
|
26
|
+
spacing: typeof BASE_TOKENS.spacing;
|
|
27
|
+
typography: typeof BASE_TOKENS.typography;
|
|
28
|
+
iconSizes: typeof BASE_TOKENS.iconSizes;
|
|
29
|
+
opacity: typeof BASE_TOKENS.opacity;
|
|
30
|
+
avatarSizes: typeof BASE_TOKENS.avatarSizes;
|
|
31
|
+
borders: typeof BASE_TOKENS.borders & {
|
|
32
|
+
card: typeof BASE_TOKENS.borders.card & { borderColor: string };
|
|
33
|
+
input: typeof BASE_TOKENS.borders.input & { borderColor: string };
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// TOKEN FACTORY FUNCTION
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create complete design tokens for a specific theme mode
|
|
43
|
+
*
|
|
44
|
+
* @param mode - Theme mode ('light' or 'dark')
|
|
45
|
+
* @param customColors - Optional custom colors to override default colors
|
|
46
|
+
* @returns Complete design tokens object
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const lightTokens = createDesignTokens('light');
|
|
51
|
+
* const darkTokens = createDesignTokens('dark');
|
|
52
|
+
* const customTokens = createDesignTokens('dark', { primary: '#FF6B35' });
|
|
53
|
+
*
|
|
54
|
+
* // Use in components
|
|
55
|
+
* <View style={{ backgroundColor: lightTokens.colors.primary }}>
|
|
56
|
+
* <Text style={lightTokens.typography.bodyLarge}>Hello!</Text>
|
|
57
|
+
* </View>
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const createDesignTokens = (
|
|
61
|
+
mode: ThemeMode,
|
|
62
|
+
customColors?: CustomThemeColors,
|
|
63
|
+
): DesignTokens => {
|
|
64
|
+
// Get color palette for theme mode
|
|
65
|
+
const baseColors = getColorPalette(mode);
|
|
66
|
+
|
|
67
|
+
// Apply custom colors if provided
|
|
68
|
+
const colors = applyCustomColors(baseColors, customColors);
|
|
69
|
+
|
|
70
|
+
// Combine static tokens + dynamic colors
|
|
71
|
+
return {
|
|
72
|
+
// ✅ DYNAMIC: Colors from theme mode + custom overrides
|
|
73
|
+
colors,
|
|
74
|
+
|
|
75
|
+
// ✅ STATIC: These don't change with theme
|
|
76
|
+
spacing: BASE_TOKENS.spacing,
|
|
77
|
+
typography: BASE_TOKENS.typography,
|
|
78
|
+
iconSizes: BASE_TOKENS.iconSizes,
|
|
79
|
+
opacity: BASE_TOKENS.opacity,
|
|
80
|
+
avatarSizes: BASE_TOKENS.avatarSizes,
|
|
81
|
+
|
|
82
|
+
// ✅ BORDERS: Static + injected border colors from theme
|
|
83
|
+
borders: {
|
|
84
|
+
...BASE_TOKENS.borders,
|
|
85
|
+
card: {
|
|
86
|
+
...BASE_TOKENS.borders.card,
|
|
87
|
+
borderColor: colors.border,
|
|
88
|
+
},
|
|
89
|
+
input: {
|
|
90
|
+
...BASE_TOKENS.borders.input,
|
|
91
|
+
borderColor: colors.border,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// UTILITY EXPORTS
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
export { withAlpha };
|
|
102
|
+
export type { ThemeMode, ColorPalette };
|
|
103
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COLOR UTILITIES
|
|
3
|
+
*
|
|
4
|
+
* Color manipulation and validation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DESIGN_CONSTANTS } from '../constants/DesignConstants';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate hex color format
|
|
11
|
+
* @param hexColor - Hex color string to validate
|
|
12
|
+
* @returns true if valid hex color
|
|
13
|
+
*/
|
|
14
|
+
export const isValidHexColor = (hexColor: string): boolean => {
|
|
15
|
+
if (!hexColor || typeof hexColor !== 'string') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return DESIGN_CONSTANTS.HEX_COLOR_REGEX.test(hexColor);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Add alpha transparency to hex color
|
|
24
|
+
* @param hexColor - Hex color string (#RRGGBB or #RGB)
|
|
25
|
+
* @param alpha - Alpha value 0-1
|
|
26
|
+
* @returns Hex color with alpha (#RRGGBBAA)
|
|
27
|
+
*/
|
|
28
|
+
export const withAlpha = (hexColor: string, alpha: number): string => {
|
|
29
|
+
if (!isValidHexColor(hexColor)) {
|
|
30
|
+
if (__DEV__) {
|
|
31
|
+
console.warn('[withAlpha] Invalid hex color format:', hexColor);
|
|
32
|
+
}
|
|
33
|
+
return hexColor;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof alpha !== 'number' || alpha < 0 || alpha > 1) {
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
console.warn('[withAlpha] Invalid alpha value, must be 0-1:', alpha);
|
|
39
|
+
}
|
|
40
|
+
return hexColor;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Convert 3-digit hex to 6-digit
|
|
44
|
+
const hex = hexColor.length === 4
|
|
45
|
+
? hexColor.split('').map(c => c + c).join('')
|
|
46
|
+
: hexColor;
|
|
47
|
+
|
|
48
|
+
const alphaHex = Math.round(alpha * 255)
|
|
49
|
+
.toString(16)
|
|
50
|
+
.padStart(2, '0');
|
|
51
|
+
|
|
52
|
+
return hex + alphaHex;
|
|
53
|
+
};
|