@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,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
+ };